diff --git a/source/tools/XpartaMuPP/README b/source/tools/XpartaMuPP/README
index a471ffceb4..cdc65a71df 100644
--- a/source/tools/XpartaMuPP/README
+++ b/source/tools/XpartaMuPP/README
@@ -61,7 +61,7 @@ To enable the bot to send the game list to players it needs the JIDs of the play
Run XpartaMuPP - XMPP Multiplayer Game Manager
==============================================
-You need to have python 3 and SleekXmpp installed
+You need to have python 3 and SleekXmpp installed (tested for 1.0-beta5)
$ sudo apt-get install python3 python3-sleekxmpp
If you would like to run the leaderboard database,
diff --git a/source/tools/XpartaMuPP/XpartaMuPP.py b/source/tools/XpartaMuPP/XpartaMuPP.py
index b3c43a5645..aff14458ba 100644
--- a/source/tools/XpartaMuPP/XpartaMuPP.py
+++ b/source/tools/XpartaMuPP/XpartaMuPP.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-"""Copyright (C) 2014 Wildfire Games.
+"""Copyright (C) 2016 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -26,251 +26,6 @@ from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin, ET
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
-from sqlalchemy import func
-
-from LobbyRanking import session as db, Game, Player, PlayerInfo
-from ELO import get_rating_adjustment
-# Rating that new players should be inserted into the
-# database with, before they've played any games.
-leaderboard_default_rating = 1200
-
-## Class that contains and manages leaderboard data ##
-class LeaderboardList():
- def __init__(self, room):
- self.room = room
- self.lastRated = ""
-
- def getProfile(self, JID):
- """
- Retrieves the profile for the specified JID
- """
- stats = {}
- player = db.query(Player).filter(Player.jid.ilike(str(JID)))
- if not player.first():
- return
- if player.first().rating != -1:
- stats['rating'] = str(player.first().rating)
-
- if player.first().highest_rating != -1:
- stats['highestRating'] = str(player.first().highest_rating)
-
- playerID = player.first().id
- players = db.query(Player).filter(Player.rating != -1).order_by(Player.rating.desc()).all()
-
- for rank, user in enumerate(players):
- if (user.jid.lower() == JID.lower()):
- stats['rank'] = str(rank+1)
- break
-
- stats['totalGamesPlayed'] = str(db.query(PlayerInfo).filter_by(player_id=playerID).count())
- stats['wins'] = str(db.query(Game).filter_by(winner_id=playerID).count())
- stats['losses'] = str(db.query(PlayerInfo).filter_by(player_id=playerID).count() - db.query(Game).filter_by(winner_id=playerID).count())
- return stats
-
- def getOrCreatePlayer(self, JID):
- """
- Stores a player(JID) in the database if they don't yet exist.
- Returns either the newly created instance of
- the Player model, or the one that already
- exists in the database.
- """
- players = db.query(Player).filter_by(jid=str(JID))
- if not players.first():
- player = Player(jid=str(JID), rating=-1)
- db.add(player)
- db.commit()
- return player
- return players.first()
-
- def removePlayer(self, JID):
- """
- Remove a player(JID) from database.
- Returns the player that was removed, or None
- if that player didn't exist.
- """
- players = db.query(Player).filter_by(jid=JID)
- player = players.first()
- if not player:
- return None
- players.delete()
- return player
-
- def addGame(self, gamereport):
- """
- Adds a game to the database and updates the data
- on a player(JID) from game results.
- Returns the created Game object, or None if
- the creation failed for any reason.
- Side effects:
- Inserts a new Game instance into the database.
- """
- # Discard any games still in progress.
- if any(map(lambda state: state == 'active',
- dict.values(gamereport['playerStates']))):
- return None
-
- players = map(lambda jid: db.query(Player).filter(Player.jid.ilike(str(jid))).first(),
- dict.keys(gamereport['playerStates']))
-
- winning_jid = list(dict.keys({jid: state for jid, state in
- gamereport['playerStates'].items()
- if state == 'won'}))[0]
- def get(stat, jid):
- return gamereport[stat][jid]
-
- singleStats = {'timeElapsed', 'mapName', 'teamsLocked', 'matchID'}
- totalScoreStats = {'economyScore', 'militaryScore', 'totalScore'}
- resourceStats = {'foodGathered', 'foodUsed', 'woodGathered', 'woodUsed',
- 'stoneGathered', 'stoneUsed', 'metalGathered', 'metalUsed', 'vegetarianFoodGathered',
- 'treasuresCollected', 'lootCollected', 'tributesSent', 'tributesReceived'}
- unitsStats = {'totalUnitsTrained', 'totalUnitsLost', 'enemytotalUnitsKilled', 'infantryUnitsTrained',
- 'infantryUnitsLost', 'enemyInfantryUnitsKilled', 'workerUnitsTrained', 'workerUnitsLost',
- 'enemyWorkerUnitsKilled', 'femaleUnitsTrained', 'femaleUnitsLost', 'enemyFemaleUnitsKilled',
- 'cavalryUnitsTrained', 'cavalryUnitsLost', 'enemyCavalryUnitsKilled', 'championUnitsTrained',
- 'championUnitsLost', 'enemyChampionUnitsKilled', 'heroUnitsTrained', 'heroUnitsLost',
- 'enemyHeroUnitsKilled', 'shipUnitsTrained', 'shipUnitsLost', 'enemyShipUnitsKilled', 'traderUnitsTrained',
- 'traderUnitsLost', 'enemyTraderUnitsKilled'}
- buildingsStats = {'totalBuildingsConstructed', 'totalBuildingsLost', 'enemytotalBuildingsDestroyed',
- 'civCentreBuildingsConstructed', 'civCentreBuildingsLost', 'enemyCivCentreBuildingsDestroyed',
- 'houseBuildingsConstructed', 'houseBuildingsLost', 'enemyHouseBuildingsDestroyed',
- 'economicBuildingsConstructed', 'economicBuildingsLost', 'enemyEconomicBuildingsDestroyed',
- 'outpostBuildingsConstructed', 'outpostBuildingsLost', 'enemyOutpostBuildingsDestroyed',
- 'militaryBuildingsConstructed', 'militaryBuildingsLost', 'enemyMilitaryBuildingsDestroyed',
- 'fortressBuildingsConstructed', 'fortressBuildingsLost', 'enemyFortressBuildingsDestroyed',
- 'wonderBuildingsConstructed', 'wonderBuildingsLost', 'enemyWonderBuildingsDestroyed'}
- marketStats = {'woodBought', 'foodBought', 'stoneBought', 'metalBought', 'tradeIncome'}
- miscStats = {'civs', 'teams', 'percentMapExplored'}
-
- stats = totalScoreStats | resourceStats | unitsStats | buildingsStats | marketStats | miscStats
- playerInfos = []
- for player in players:
- jid = player.jid
- playerinfo = PlayerInfo(player=player)
- for reportname in stats:
- setattr(playerinfo, reportname, get(reportname, jid.lower()))
- playerInfos.append(playerinfo)
-
- game = Game(map=gamereport['mapName'], duration=int(gamereport['timeElapsed']), teamsLocked=bool(gamereport['teamsLocked']), matchID=gamereport['matchID'])
- game.players.extend(players)
- game.player_info.extend(playerInfos)
- game.winner = db.query(Player).filter(Player.jid.ilike(str(winning_jid))).first()
- db.add(game)
- db.commit()
- return game
-
- def verifyGame(self, gamereport):
- """
- Returns a boolean based on whether the game should be rated.
- Here, we can specify the criteria for rated games.
- """
- winning_jids = list(dict.keys({jid: state for jid, state in
- gamereport['playerStates'].items()
- if state == 'won'}))
- # We only support 1v1s right now. TODO: Support team games.
- if len(winning_jids) * 2 > len(dict.keys(gamereport['playerStates'])):
- # More than half the people have won. This is not a balanced team game or duel.
- return False
- if len(dict.keys(gamereport['playerStates'])) != 2:
- return False
- return True
-
- def rateGame(self, game):
- """
- Takes a game with 2 players and alters their ratings
- based on the result of the game.
- Returns self.
- Side effects:
- Changes the game's players' ratings in the database.
- """
- player1 = game.players[0]
- player2 = game.players[1]
- # TODO: Support draws. Since it's impossible to draw in the game currently,
- # the database model, and therefore this code, requires a winner.
- # The Elo implementation does not, however.
- result = 1 if player1 == game.winner else -1
- # Player's ratings are -1 unless they have played a rated game.
- if player1.rating == -1:
- player1.rating = leaderboard_default_rating
- if player2.rating == -1:
- player2.rating = leaderboard_default_rating
-
- rating_adjustment1 = int(get_rating_adjustment(player1.rating, player2.rating,
- len(player1.games), len(player2.games), result))
- rating_adjustment2 = int(get_rating_adjustment(player2.rating, player1.rating,
- len(player2.games), len(player1.games), result * -1))
- if result == 1:
- resultQualitative = "won"
- elif result == 0:
- resultQualitative = "drew"
- else:
- resultQualitative = "lost"
- name1 = '@'.join(player1.jid.split('@')[:-1])
- name2 = '@'.join(player2.jid.split('@')[:-1])
- self.lastRated = "A rated game has ended. %s %s against %s. Rating Adjustment: %s (%s -> %s) and %s (%s -> %s)."%(name1,
- resultQualitative, name2, name1, player1.rating, player1.rating + rating_adjustment1,
- name2, player2.rating, player2.rating + rating_adjustment2)
- player1.rating += rating_adjustment1
- player2.rating += rating_adjustment2
- if not player1.highest_rating:
- player1.highest_rating = -1
- if not player2.highest_rating:
- player2.highest_rating = -1
- if player1.rating > player1.highest_rating:
- player1.highest_rating = player1.rating
- if player2.rating > player2.highest_rating:
- player2.highest_rating = player2.rating
- db.commit()
- return self
-
- def getLastRatedMessage(self):
- """
- Gets the string of the last rated game. Triggers an update
- chat for the bot.
- """
- return self.lastRated
-
- def addAndRateGame(self, gamereport):
- """
- Calls addGame and if the game has only two
- players, also calls rateGame.
- Returns the result of addGame.
- """
- game = self.addGame(gamereport)
- if game and self.verifyGame(gamereport):
- self.rateGame(game)
- else:
- self.lastRated = ""
- return game
-
- def getBoard(self):
- """
- Returns a dictionary of player rankings to
- JIDs for sending.
- """
- board = {}
- players = db.query(Player).filter(Player.rating != -1).order_by(Player.rating.desc()).limit(100).all()
- for rank, player in enumerate(players):
- board[player.jid] = {'name': '@'.join(player.jid.split('@')[:-1]), 'rating': str(player.rating)}
- return board
- def getRatingList(self, nicks):
- """
- Returns a rating list of players
- currently in the lobby by nick
- because the client can't link
- JID to nick conveniently.
- """
- ratinglist = {}
- for JID in list(nicks):
- players = db.query(Player.jid, Player.rating).filter(Player.jid.ilike(str(JID)))
- if players.first():
- if players.first().rating == -1:
- ratinglist[nicks[JID]] = {'name': nicks[JID], 'rating': ''}
- else:
- ratinglist[nicks[JID]] = {'name': nicks[JID], 'rating': str(players.first().rating)}
- else:
- ratinglist[nicks[JID]] = {'name': nicks[JID], 'rating': ''}
- return ratinglist
-
## Class to tracks all games in the lobby ##
class GameList():
def __init__(self):
@@ -310,101 +65,17 @@ class GameList():
if 'startTime' not in self.gameList[JID]:
self.gameList[JID]['startTime'] = str(round(time.time()))
-## Class which manages different game reports from clients ##
-## and calls leaderboard functions as appropriate. ##
-class ReportManager():
- def __init__(self, leaderboard):
- self.leaderboard = leaderboard
- self.interimReportTracker = []
- self.interimJIDTracker = []
+## Class for custom player stanza extension ##
+class PlayerXmppPlugin(ElementBase):
+ name = 'query'
+ namespace = 'jabber:iq:player'
+ interfaces = set(('online'))
+ sub_interfaces = interfaces
+ plugin_attrib = 'player'
- def addReport(self, JID, rawGameReport):
- """
- Adds a game to the interface between a raw report
- and the leaderboard database.
- """
- # cleanRawGameReport is a copy of rawGameReport with all reporter specific information removed.
- cleanRawGameReport = rawGameReport.copy()
- del cleanRawGameReport["playerID"]
-
- if cleanRawGameReport not in self.interimReportTracker:
- # Store the game.
- appendIndex = len(self.interimReportTracker)
- self.interimReportTracker.append(cleanRawGameReport)
- # Initilize the JIDs and store the initial JID.
- numPlayers = self.getNumPlayers(rawGameReport)
- JIDs = [None] * numPlayers
- if numPlayers - int(rawGameReport["playerID"]) > -1:
- JIDs[int(rawGameReport["playerID"])-1] = str(JID).lower()
- self.interimJIDTracker.append(JIDs)
- else:
- # We get the index at which the JIDs coresponding to the game are stored.
- index = self.interimReportTracker.index(cleanRawGameReport)
- # We insert the new report JID into the acending list of JIDs for the game.
- JIDs = self.interimJIDTracker[index]
- if len(JIDs) - int(rawGameReport["playerID"]) > -1:
- JIDs[int(rawGameReport["playerID"])-1] = str(JID).lower()
- self.interimJIDTracker[index] = JIDs
-
- self.checkFull()
-
- def expandReport(self, rawGameReport, JIDs):
- """
- Takes an raw game report and re-formats it into
- Python data structures leaving JIDs empty.
- Returns a processed gameReport of type dict.
- """
- processedGameReport = {}
- for key in rawGameReport:
- if rawGameReport[key].find(",") == -1:
- processedGameReport[key] = rawGameReport[key]
- else:
- split = rawGameReport[key].split(",")
- # Remove the false split positive.
- split.pop()
- statToJID = {}
- for i, part in enumerate(split):
- statToJID[JIDs[i]] = part
- processedGameReport[key] = statToJID
- return processedGameReport
-
- def checkFull(self):
- """
- Searches internal database to check if enough
- reports have been submitted to add a game to
- the leaderboard. If so, the report will be
- interpolated and addAndRateGame will be
- called with the result.
- """
- i = 0
- length = len(self.interimReportTracker)
- while(i < length):
- numPlayers = self.getNumPlayers(self.interimReportTracker[i])
- numReports = 0
- for JID in self.interimJIDTracker[i]:
- if JID != None:
- numReports += 1
- if numReports == numPlayers:
- self.leaderboard.addAndRateGame(self.expandReport(self.interimReportTracker[i], self.interimJIDTracker[i]))
- del self.interimJIDTracker[i]
- del self.interimReportTracker[i]
- length -= 1
- else:
- i += 1
- self.leaderboard.lastRated = ""
-
- def getNumPlayers(self, rawGameReport):
- """
- Computes the number of players in a raw gameReport.
- Returns int, the number of players.
- """
- # Find a key in the report which holds values for multiple players.
- for key in rawGameReport:
- if rawGameReport[key].find(",") != -1:
- # Count the number of values, minus one for the false split positive.
- return len(rawGameReport[key].split(","))-1
- # Return -1 in case of failure.
- return -1
+ def addPlayerOnline(self, player):
+ playerXml = ET.fromstring("%s" % player)
+ self.xml.append(playerXml)
## Class for custom gamelist stanza extension ##
class GameListXmppPlugin(ElementBase):
@@ -433,12 +104,15 @@ class GameListXmppPlugin(ElementBase):
class BoardListXmppPlugin(ElementBase):
name = 'query'
namespace = 'jabber:iq:boardlist'
- interfaces = set(('board', 'command'))
+ interfaces = set(('board', 'command', 'recipient'))
sub_interfaces = interfaces
plugin_attrib = 'boardlist'
def addCommand(self, command):
commandXml = ET.fromstring("%s" % command)
self.xml.append(commandXml)
+ def addRecipient(self, recipient):
+ recipientXml = ET.fromstring("%s" % recipient)
+ self.xml.append(recipientXml)
def addItem(self, name, rating):
itemXml = ET.Element("board", {"name": name, "rating": rating})
self.xml.append(itemXml)
@@ -448,9 +122,14 @@ class GameReportXmppPlugin(ElementBase):
name = 'report'
namespace = 'jabber:iq:gamereport'
plugin_attrib = 'gamereport'
- interfaces = ('game')
+ interfaces = ('game', 'sender')
sub_interfaces = interfaces
-
+ def addSender(self, sender):
+ senderXml = ET.fromstring("%s" % sender)
+ self.xml.append(senderXml)
+ def addGame(self, gr):
+ game = ET.fromstring(str(gr)).find('{%s}game' % self.namespace)
+ self.xml.append(game)
def getGame(self):
"""
Required to parse incoming stanzas with this
@@ -466,12 +145,15 @@ class GameReportXmppPlugin(ElementBase):
class ProfileXmppPlugin(ElementBase):
name = 'query'
namespace = 'jabber:iq:profile'
- interfaces = set(('profile', 'command'))
+ interfaces = set(('profile', 'command', 'recipient'))
sub_interfaces = interfaces
plugin_attrib = 'profile'
def addCommand(self, command):
commandXml = ET.fromstring("%s" % command)
self.xml.append(commandXml)
+ def addRecipient(self, recipient):
+ recipientXml = ET.fromstring("%s" % recipient)
+ self.xml.append(recipientXml)
def addItem(self, player, rating, highestRating, rank, totalGamesPlayed, wins, losses):
itemXml = ET.Element("profile", {"player": player, "rating": rating, "highestRating": highestRating,
"rank" : rank, "totalGamesPlayed" : totalGamesPlayed, "wins" : wins,
@@ -483,32 +165,32 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
"""
A simple list provider
"""
- def __init__(self, sjid, password, room, nick):
+ def __init__(self, sjid, password, room, nick, ratingsbot):
sleekxmpp.ClientXMPP.__init__(self, sjid, password)
self.sjid = sjid
self.room = room
self.nick = nick
+ self.ratingsBotWarned = False
- self.ratingListUpdate = False
+ self.ratingsBot = ratingsbot
# Game collection
self.gameList = GameList()
- # Init leaderboard object
- self.leaderboard = LeaderboardList(room)
-
- # gameReport to leaderboard abstraction
- self.reportManager = ReportManager(self.leaderboard)
-
# Store mapping of nicks and XmppIDs, attached via presence stanza
self.nicks = {}
self.lastLeft = ""
+ register_stanza_plugin(Iq, PlayerXmppPlugin)
register_stanza_plugin(Iq, GameListXmppPlugin)
register_stanza_plugin(Iq, BoardListXmppPlugin)
register_stanza_plugin(Iq, GameReportXmppPlugin)
register_stanza_plugin(Iq, ProfileXmppPlugin)
+ self.register_handler(Callback('Iq Player',
+ StanzaPath('iq/player'),
+ self.iqhandler,
+ instream=True))
self.register_handler(Callback('Iq Gamelist',
StanzaPath('iq/gamelist'),
self.iqhandler,
@@ -521,7 +203,6 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
StanzaPath('iq/gamereport'),
self.iqhandler,
instream=True))
-
self.register_handler(Callback('Iq Profile',
StanzaPath('iq/profile'),
self.iqhandler,
@@ -545,7 +226,9 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
"""
Process presence stanza from a chat room.
"""
- self.ratingListUpdate = True
+ if self.ratingsBot in self.nicks:
+ self.relayRatingListRequest(self.ratingsBot)
+ self.relayPlayerOnline(presence['muc']['jid'])
if presence['muc']['nick'] != self.nick:
# If it doesn't already exist, store player JID mapped to their nick.
if str(presence['muc']['jid']) not in self.nicks:
@@ -553,12 +236,6 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
# Check the jid isn't already in the lobby.
# Send Gamelist to new player.
self.sendGameList(presence['muc']['jid'])
- # Following two calls make sqlalchemy complain about using objects in the
- # incorrect thread. TODO: Figure out how to fix this.
- # Send Leaderboard to new player.
- #self.sendBoardList(presence['muc']['jid'])
- # Register on leaderboard.
- #self.leaderboard.getOrCreatePlayer(presence['muc']['jid'])
logging.debug("Client '%s' connected with a nick of '%s'." %(presence['muc']['jid'], presence['muc']['nick']))
def muc_offline(self, presence):
@@ -577,6 +254,8 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
self.lastLeft = str(presence['muc']['jid'])
if str(presence['muc']['jid']) in self.nicks:
del self.nicks[str(presence['muc']['jid'])]
+ if presence['muc']['nick'] == self.ratingsBot:
+ self.ratingsBotWarned = False
def muc_message(self, msg):
"""
@@ -609,31 +288,32 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
logging.error("Failed to process gamelist request from %s" % iq['from'].bare)
elif 'boardlist' in iq.plugins:
command = iq['boardlist']['command']
- if command == 'getleaderboard':
- try:
- self.leaderboard.getOrCreatePlayer(iq['from'])
- self.sendBoardList(iq['from'])
- except:
- traceback.print_exc()
- logging.error("Failed to process leaderboardlist request from %s" % iq['from'].bare)
- else:
- logging.error("Failed to process boardlist request from %s" % iq['from'].bare)
+ try:
+ self.relayBoardListRequest(iq['from'])
+ except:
+ traceback.print_exc()
+ logging.error("Failed to process leaderboardlist request from %s" % iq['from'].bare)
elif 'profile' in iq.plugins:
command = iq['profile']['command']
try:
- self.sendProfile(iq['from'], command)
+ self.relayProfileRequest(iq['from'], command)
except:
- try:
- self.sendProfileNotFound(iq['from'], command)
- except:
- logging.debug("No record found for %s" % command)
+ pass
else:
logging.error("Unknown 'get' type stanza request from %s" % iq['from'].bare)
elif iq['type'] == 'result':
"""
Iq successfully received
"""
- pass
+ if 'boardlist' in iq.plugins:
+ recipient = iq['boardlist']['recipient']
+ self.relayBoardList(iq['boardlist'], recipient)
+ elif 'profile' in iq.plugins:
+ recipient = iq['profile']['recipient']
+ player = iq['profile']['command']
+ self.relayProfile(iq['profile'], player, recipient)
+ else:
+ pass
elif iq['type'] == 'set':
if 'gamelist' in iq.plugins:
"""
@@ -672,20 +352,12 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
Client is reporting end of game statistics
"""
try:
- self.reportManager.addReport(iq['from'], iq['gamereport']['game'])
- if self.leaderboard.getLastRatedMessage() != "":
- self.send_message(mto=self.room, mbody=self.leaderboard.getLastRatedMessage(), mtype="groupchat",
- mnick=self.nick)
- self.sendBoardList()
- self.sendRatingList()
+ self.relayGameReport(iq['gamereport'], iq['from'])
except:
traceback.print_exc()
logging.error("Failed to update game statistics for %s" % iq['from'].bare)
else:
logging.error("Failed to process stanza type '%s' received from %s" % iq['type'], iq['from'].bare)
- if self.ratingListUpdate == True:
- self.sendRatingList()
- self.ratingListUpdate = False
def sendGameList(self, to = ""):
"""
@@ -738,22 +410,129 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
except:
logging.error("Failed to send game list")
- def sendBoardList(self, to = ""):
+ def relayBoardListRequest(self, recipient):
+ """
+ Send a boardListRequest to EcheLOn.
+ """
+ to = self.ratingsBot
+ if to not in self.nicks:
+ self.warnRatingsBotOffline()
+ return
+ stz = BoardListXmppPlugin()
+ iq = self.Iq()
+ iq['type'] = 'get'
+ stz.addCommand('getleaderboard')
+ stz.addRecipient(recipient)
+ iq.setPayload(stz)
+ ## Set additional IQ attributes
+ iq['to'] = to
+ ## Try sending the stanza
+ try:
+ iq.send(block=False, now=True)
+ except:
+ logging.error("Failed to send leaderboard list request")
+
+ def relayRatingListRequest(self, recipient):
+ """
+ Send a ratingListRequest to EcheLOn.
+ """
+ to = self.ratingsBot
+ if to not in self.nicks:
+ self.warnRatingsBotOffline()
+ return
+ stz = BoardListXmppPlugin()
+ iq = self.Iq()
+ iq['type'] = 'get'
+ stz.addCommand('getratinglist')
+ iq.setPayload(stz)
+ ## Set additional IQ attributes
+ iq['to'] = to
+ ## Try sending the stanza
+ try:
+ iq.send(block=False, now=True)
+ except:
+ logging.error("Failed to send rating list request")
+
+ def relayProfileRequest(self, recipient, player):
+ """
+ Send a profileRequest to EcheLOn.
+ """
+ to = self.ratingsBot
+ if to not in self.nicks:
+ self.warnRatingsBotOffline()
+ return
+ stz = ProfileXmppPlugin()
+ iq = self.Iq()
+ iq['type'] = 'get'
+ stz.addCommand(player)
+ stz.addRecipient(recipient)
+ iq.setPayload(stz)
+ ## Set additional IQ attributes
+ iq['to'] = to
+ ## Try sending the stanza
+ try:
+ iq.send(block=False, now=True)
+ except:
+ logging.error("Failed to send profile request")
+
+ def relayPlayerOnline(self, jid):
+ """
+ Tells EcheLOn that someone comes online.
+ """
+ ## Check recipient exists
+ to = self.ratingsBot
+ if to not in self.nicks:
+ return
+ stz = PlayerXmppPlugin()
+ iq = self.Iq()
+ iq['type'] = 'set'
+ stz.addPlayerOnline(jid)
+ iq.setPayload(stz)
+ ## Set additional IQ attributes
+ iq['to'] = to
+ ## Try sending the stanza
+ try:
+ iq.send(block=False, now=True)
+ except:
+ logging.error("Failed to send player muc online")
+
+ def relayGameReport(self, data, sender):
+ """
+ Relay a game report to EcheLOn.
+ """
+ to = self.ratingsBot
+ if to not in self.nicks:
+ self.warnRatingsBotOffline()
+ return
+ stz = GameReportXmppPlugin()
+ stz.addGame(data)
+ stz.addSender(sender)
+ iq = self.Iq()
+ iq['type'] = 'set'
+ iq.setPayload(stz)
+ ## Set additional IQ attributes
+ iq['to'] = to
+ ## Try sending the stanza
+ try:
+ iq.send(block=False, now=True)
+ except:
+ logging.error("Failed to send game report request")
+
+ def relayBoardList(self, boardList, to = ""):
"""
Send the whole leaderboard list.
If no target is passed the boardlist is broadcasted
to all clients.
"""
- ## Pull leaderboard data and add it to the stanza
- board = self.leaderboard.getBoard()
- stz = BoardListXmppPlugin()
iq = self.Iq()
iq['type'] = 'result'
- for i in board:
+ """for i in board:
stz.addItem(board[i]['name'], board[i]['rating'])
- stz.addCommand('boardlist')
- iq.setPayload(stz)
- if to == "":
+ stz.addCommand('boardlist')"""
+ iq.setPayload(boardList)
+ ## Check recipient exists
+ if to == "":
+ # Rating List
for JID in list(self.nicks):
## Set additional IQ attributes
iq['to'] = JID
@@ -761,9 +540,9 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
try:
iq.send(block=False, now=True)
except:
- logging.error("Failed to send leaderboard list")
+ logging.error("Failed to send rating list")
else:
- ## Check recipient exists
+ # Leaderboard
if str(to) not in self.nicks:
logging.error("No player with the XmPP ID '%s' known to send boardlist to" % str(to))
return
@@ -775,68 +554,17 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
except:
logging.error("Failed to send leaderboard list")
- def sendRatingList(self, to = ""):
+ def relayProfile(self, data, player, to):
"""
- Send the rating list.
- If no target is passed the rating list is broadcasted
- to all clients.
- """
- ## Pull rating list data and add it to the stanza
- ratinglist = self.leaderboard.getRatingList(self.nicks)
- stz = BoardListXmppPlugin()
- iq = self.Iq()
- iq['type'] = 'result'
- for i in ratinglist:
- stz.addItem(ratinglist[i]['name'], ratinglist[i]['rating'])
- stz.addCommand('ratinglist')
- iq.setPayload(stz)
- if to == "":
- for JID in list(self.nicks):
- ## Set additional IQ attributes
- iq['to'] = JID
- ## Try sending the stanza
- try:
- iq.send(block=False, now=True)
- except:
- logging.error("Failed to send rating list")
- else:
- ## Check recipient exists
- if str(to) not in self.nicks:
- logging.error("No player with the XmPP ID '%s' known to send ratinglist to" % str(to))
- return
- ## Set additional IQ attributes
- iq['to'] = to
- ## Try sending the stanza
- try:
- iq.send(block=False, now=True)
- except:
- logging.error("Failed to send rating list")
-
- def sendProfile(self, to, player):
- """
- Send the profile to a specified player.
+ Send the player profile to a specified target.
"""
if to == "":
- logging.error("Failed to send profile")
+ logging.error("Failed to send profile, target unspecified")
return
- online = False;
- ## Pull stats and add it to the stanza
- for JID in list(self.nicks):
- if self.nicks[JID] == player:
- stats = self.leaderboard.getProfile(JID)
- online = True
- break
-
- if online == False:
- stats = self.leaderboard.getProfile(player + "@" + str(to).split('@')[1])
- stz = ProfileXmppPlugin()
iq = self.Iq()
iq['type'] = 'result'
-
- stz.addItem(player, stats['rating'], stats['highestRating'], stats['rank'], stats['totalGamesPlayed'], stats['wins'], stats['losses'])
- stz.addCommand(player)
- iq.setPayload(stz)
+ iq.setPayload(data)
## Check recipient exists
if str(to) not in self.nicks:
logging.error("No player with the XmPP ID '%s' known to send profile to" % str(to))
@@ -852,32 +580,13 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
traceback.print_exc()
logging.error("Failed to send profile")
- def sendProfileNotFound(self, to, player):
+ def warnRatingsBotOffline(self):
"""
- Send a profile not-found error to a specified player.
+ Warns that the ratings bot is offline.
"""
- stz = ProfileXmppPlugin()
- iq = self.Iq()
- iq['type'] = 'result'
-
- filler = str(0)
- stz.addItem(player, str(-2), filler, filler, filler, filler, filler)
- stz.addCommand(player)
- iq.setPayload(stz)
- ## Check recipient exists
- if str(to) not in self.nicks:
- logging.error("No player with the XmPP ID '%s' known to send profile to" % str(to))
- return
-
- ## Set additional IQ attributes
- iq['to'] = to
-
- ## Try sending the stanza
- try:
- iq.send(block=False, now=True)
- except:
- traceback.print_exc()
- logging.error("Failed to send profile")
+ if not self.ratingsBotWarned:
+ logging.warn("Ratings bot '%s' is offline" % str(self.ratingsBot))
+ self.ratingsBotWarned = True
## Main Program ##
if __name__ == '__main__':
@@ -911,6 +620,9 @@ if __name__ == '__main__':
optp.add_option('-r', '--room', help='set muc room to join',
action='store', dest='xroom',
default="arena")
+ optp.add_option('-e', '--elo', help='set rating bot username',
+ action='store', dest='xratingsbot',
+ default="disabled")
opts, args = optp.parse_args()
@@ -919,7 +631,7 @@ if __name__ == '__main__':
format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
# XpartaMuPP
- xmpp = XpartaMuPP(opts.xlogin+'@'+opts.xdomain+'/CC', opts.xpassword, opts.xroom+'@conference.'+opts.xdomain, opts.xnickname)
+ xmpp = XpartaMuPP(opts.xlogin+'@'+opts.xdomain+'/CC', opts.xpassword, opts.xroom+'@conference.'+opts.xdomain, opts.xnickname, opts.xratingsbot+'@'+opts.xdomain+'/CC')
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0045') # Multi-User Chat # used