Adds profiles to the multiplayer lobby. Fixes #2504.

This was SVN commit r15768.
This commit is contained in:
scythetwirler 2014-09-20 15:35:26 +00:00
parent d62e3729d5
commit 6b2677a3fd
12 changed files with 566 additions and 20 deletions

View File

@ -190,6 +190,126 @@ function updatePlayerList()
return [playerList, presenceList, nickList, ratingList];
}
/**
* Display the profile of the selected player.
* Displays N/A for all stats until updateProfile is called when the stats
* are actually received from the bot.
*
* @param caller From which screen is the user requesting data from?
*/
function displayProfile(caller)
{
var playerList, rating;
if (caller == "leaderboard")
playerList = Engine.GetGUIObjectByName("leaderboardBox");
else if (caller == "lobbylist")
playerList = Engine.GetGUIObjectByName("playersBox");
else if (caller == "fetch")
{
Engine.SendGetProfile(Engine.GetGUIObjectByName("fetchInput").caption);
return;
}
else
return;
if (!playerList.list[playerList.selected])
{
Engine.GetGUIObjectByName("profileArea").hidden = true;
return;
}
Engine.GetGUIObjectByName("profileArea").hidden = false;
Engine.SendGetProfile(playerList.list[playerList.selected]);
var user = playerList.list_name[playerList.selected];
var role = Engine.LobbyGetPlayerRole(playerList.list[playerList.selected]);
var userList = Engine.GetGUIObjectByName("playersBox");
if (role && caller == "lobbylist")
{
// Make the role uppercase.
role = role.charAt(0).toUpperCase() + role.slice(1);
if (role == "Moderator")
role = '[color="0 125 0"]' + translate(role) + '[/color]';
}
else
role = "";
Engine.GetGUIObjectByName("usernameText").caption = user;
Engine.GetGUIObjectByName("roleText").caption = translate(role);
Engine.GetGUIObjectByName("rankText").caption = translate("N/A");
Engine.GetGUIObjectByName("highestRatingText").caption = translate("N/A");
Engine.GetGUIObjectByName("totalGamesText").caption = translate("N/A");
Engine.GetGUIObjectByName("winsText").caption = translate("N/A");
Engine.GetGUIObjectByName("lossesText").caption = translate("N/A");
Engine.GetGUIObjectByName("ratioText").caption = translate("N/A");
}
/**
* Update the profile of the selected player with data from the bot.
*
*/
function updateProfile()
{
var playerList, user;
var attributes = Engine.GetProfile();
if (!Engine.GetGUIObjectByName("profileFetch").hidden)
{
user = attributes[0].player;
if (attributes[0].rating == "-2") // Profile not found code
{
Engine.GetGUIObjectByName("profileWindowArea").hidden = true;
Engine.GetGUIObjectByName("profileErrorText").hidden = false;
return;
}
Engine.GetGUIObjectByName("profileWindowArea").hidden = false;
Engine.GetGUIObjectByName("profileErrorText").hidden = true;
if (attributes[0].rating != "")
user = sprintf(translate("%(nick)s (%(rating)s)"), { nick: user, rating: attributes[0].rating });
Engine.GetGUIObjectByName("profileUsernameText").caption = user;
Engine.GetGUIObjectByName("profileRankText").caption = attributes[0].rank;
Engine.GetGUIObjectByName("profileHighestRatingText").caption = attributes[0].highestRating;
Engine.GetGUIObjectByName("profileTotalGamesText").caption = attributes[0].totalGamesPlayed;
Engine.GetGUIObjectByName("profileWinsText").caption = attributes[0].wins;
Engine.GetGUIObjectByName("profileLossesText").caption = attributes[0].losses;
var winRate = (attributes[0].wins / attributes[0].totalGamesPlayed * 100).toFixed(2);
if (attributes[0].totalGamesPlayed != 0)
Engine.GetGUIObjectByName("profileRatioText").caption = sprintf(translate("%(percentage)s%%"), { percentage: winRate });
else
Engine.GetGUIObjectByName("profileRatioText").caption = translate("-");
return;
}
else if (!Engine.GetGUIObjectByName("leaderboard").hidden)
playerList = Engine.GetGUIObjectByName("leaderboardBox");
else
playerList = Engine.GetGUIObjectByName("playersBox");
if (attributes[0].rating == "-2")
return;
// Make sure the stats we have received coincide with the selected player.
if (attributes[0].player != playerList.list[playerList.selected])
return;
user = playerList.list_name[playerList.selected];
if (attributes[0].rating != "")
user = sprintf(translate("%(nick)s (%(rating)s)"), { nick: user, rating: attributes[0].rating });
Engine.GetGUIObjectByName("usernameText").caption = user;
Engine.GetGUIObjectByName("rankText").caption = attributes[0].rank;
Engine.GetGUIObjectByName("highestRatingText").caption = attributes[0].highestRating;
Engine.GetGUIObjectByName("totalGamesText").caption = attributes[0].totalGamesPlayed;
Engine.GetGUIObjectByName("winsText").caption = attributes[0].wins;
Engine.GetGUIObjectByName("lossesText").caption = attributes[0].losses;
var winRate = (attributes[0].wins / attributes[0].totalGamesPlayed * 100).toFixed(2);
if (attributes[0].totalGamesPlayed != 0)
Engine.GetGUIObjectByName("ratioText").caption = sprintf(translate("%(percentage)s%%"), { percentage: winRate });
else
Engine.GetGUIObjectByName("ratioText").caption = translate("-");
}
/**
* Update the leaderboard from data cached in C++.
*/
@ -592,8 +712,11 @@ function onTick()
case "ratinglist updated":
updatePlayerList();
break;
case "profile updated":
updateProfile();
break;
}
break
break;
}
break;
default:

View File

@ -19,7 +19,7 @@
</action>
<!-- Left panel: Player list. -->
<object name="leftPanel" size="20 30 20% 100%-50">
<object name="leftPanel" size="20 30 20% 100%-280">
<object name="playersBox" style="ModernList" type="olist" size="0 0 100% 100%" font="sans-bold-stroke-13">
<def id="status" width="26%">
<translatableAttribute id="heading">Status</translatableAttribute>
@ -30,13 +30,60 @@
<def id="rating" width="24%">
<translatableAttribute id="heading">Rating</translatableAttribute>
</def>
<action on="SelectionChange">
displayProfile("lobbylist");
</action>
</object>
</object>
<object name="leftButtonPanel" size="20 100%-45 20% 100%-20">
<object type="button" style="ModernButtonRed" size="0 0 100% 100%">
<object name="profilePanel" size="20 100%-275 20% 100%-80">
<object name="profileBox" type="image" sprite="ModernDarkBoxGold" size="0 0 100% 100%">
<object name="profileArea" size="0 0 100% 100%" hidden="true">
<object name="usernameText" size="0 0 100% 45" type="text" style="ModernLabelText" text_align="center" font="sans-bold-16" />
<object name="roleText" size="0 45 100% 70" type="text" style="ModernLabelText" text_align="center" font="sans-bold-stroke-12" />
<object size="0 70 40%+40 90" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Current Rank:</translatableAttribute>
</object>
<object name="rankText" size="40%+45 70 100% 90" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 90 40%+40 110" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Highest Rating:</translatableAttribute>
</object>
<object name="highestRatingText" size="40%+45 90 100% 110" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 110 40%+40 130" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Total Games:</translatableAttribute>
</object>
<object name="totalGamesText" size="40%+45 110 100% 130" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 130 40%+40 150" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Wins:</translatableAttribute>
</object>
<object name="winsText" size="40%+45 130 100% 150" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 150 40%+40 170" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Losses:</translatableAttribute>
</object>
<object name="lossesText" size="40%+45 150 100% 170" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 170 40%+40 190" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Win Rate:</translatableAttribute>
</object>
<object name="ratioText" size="40%+45 170 100% 190" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
</object>
</object>
</object>
<object name="leftButtonPanel" size="20 100%-75 20% 100%-20">
<object type="button" style="ModernButtonRed" size="0 0 100% 25">
<translatableAttribute id="caption">Leaderboard</translatableAttribute>
<action on="Press">Engine.GetGUIObjectByName("leaderboard").hidden = false;Engine.GetGUIObjectByName("leaderboardFade").hidden = false;</action>
<action on="Press">
Engine.GetGUIObjectByName("leaderboard").hidden = false;
Engine.GetGUIObjectByName("fade").hidden = false;
displayProfile("leaderboard");
</action>
</object>
<object type="button" style="ModernButtonRed" size="0 30 100% 100%">
<translatableAttribute id="caption">User Profile Lookup</translatableAttribute>
<action on="Press">
Engine.GetGUIObjectByName("profileFetch").hidden = false;
Engine.GetGUIObjectByName("fade").hidden = false;
</action>
</object>
</object>
@ -191,7 +238,7 @@
<!-- START Window for leaderboard stats -->
<!-- Add a translucent black background to fade out the menu page -->
<object hidden="true" name="leaderboardFade" type="image" z="100" sprite="ModernFade"/>
<object hidden="true" name="fade" type="image" z="100" sprite="ModernFade"/>
<object hidden="true" name="leaderboard" type="image" style="ModernDialog" size="50%-224 50%-160 50%+224 50%+160" z="101">
<object style="ModernLabelText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">Leaderboard</translatableAttribute>
@ -209,10 +256,17 @@
<def id="rating" color="255 255 255" width="30%">
<translatableAttribute id="heading">Rating</translatableAttribute>
</def>
<action on="SelectionChange">
displayProfile("leaderboard");
</action>
</object>
<object type="button" style="ModernButtonRed" size="50%-133 100%-45 50%-5 100%-17">
<translatableAttribute id="caption">Back</translatableAttribute>
<action on="Press">Engine.GetGUIObjectByName("leaderboard").hidden = true;Engine.GetGUIObjectByName("leaderboardFade").hidden = true;</action>
<action on="Press">
Engine.GetGUIObjectByName("leaderboard").hidden = true;
Engine.GetGUIObjectByName("fade").hidden = true;
displayProfile("lobbylist");
</action>
</object>
<object type="button" style="ModernButtonRed" size="50%+5 100%-45 50%+133 100%-17">
<translatableAttribute id="caption">Update</translatableAttribute>
@ -220,5 +274,61 @@
</object>
</object>
<!-- END Window for leaderboard stats -->
<object hidden="true" name="profileFetch" type="image" style="ModernDialog" size="50%-224 50%-160 50%+224 50%+160" z="102">
<object style="ModernLabelText" type="text" size="50%-128 -18 50%+128 14">
<translatableAttribute id="caption">User Profile Lookup</translatableAttribute>
</object>
<object type="text" size="15 25 40% 50" text_align="right" textcolor="white">
<translatableAttribute id="caption">Enter username:</translatableAttribute>
</object>
<object name="fetchInput" size="40%+10 25 100%-25 50" type="input" style="ModernInput" font="sans-13">
<action on="Press">displayProfile("fetch");</action>
</object>
<object type="button" style="ModernButtonRed" size="50%-64 60 50%+64 85">
<translatableAttribute id="caption">View Profile</translatableAttribute>
<action on="Press">displayProfile("fetch");</action>
</object>
<object name="profileWindowPanel" size="25 95 100%-25 100%-60">
<object name="profileWindowBox" type="image" sprite="ModernDarkBoxGold" size="0 0 100% 100%">
<object name="profileWindowArea" size="0 0 100% 100%" hidden="false">
<object name="profileUsernameText" size="0 0 100% 25" type="text" style="ModernLabelText" text_align="center" font="sans-bold-16" />
<object size="0 30 40%+40 50" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Current Rank:</translatableAttribute>
</object>
<object name="profileRankText" size="40%+45 30 100% 50" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 50 40%+40 70" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Highest Rating:</translatableAttribute>
</object>
<object name="profileHighestRatingText" size="40%+45 50 100% 70" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 70 40%+40 90" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Total Games:</translatableAttribute>
</object>
<object name="profileTotalGamesText" size="40%+45 70 100% 90" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 90 40%+40 110" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Wins:</translatableAttribute>
</object>
<object name="profileWinsText" size="40%+45 90 100% 110" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 110 40%+40 130" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Losses:</translatableAttribute>
</object>
<object name="profileLossesText" size="40%+45 110 100% 130" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
<object size="0 130 40%+40 150" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
<translatableAttribute id="caption">Win Rate:</translatableAttribute>
</object>
<object name="profileRatioText" size="40%+45 130 100% 150" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
</object>
<object name="profileErrorText" size="25% 25% 75% 75%" type="text" style="ModernLabelText" text_align="center" font="sans-bold-stroke-13" hidden="true">
<translatableAttribute id="caption">Player not found.</translatableAttribute>
</object>
</object>
</object>
<object type="button" style="ModernButtonRed" size="50%-64 100%-50 50%+64 100%-25">
<translatableAttribute id="caption">Back</translatableAttribute>
<action on="Press">
Engine.GetGUIObjectByName("profileFetch").hidden = true;
Engine.GetGUIObjectByName("fade").hidden = true;
</action>
</object>
</object>
</object>
</objects>

View File

@ -1027,6 +1027,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, &JSI_Lobby::SendGetGameList>("SendGetGameList");
scriptInterface.RegisterFunction<void, &JSI_Lobby::SendGetBoardList>("SendGetBoardList");
scriptInterface.RegisterFunction<void, &JSI_Lobby::SendGetRatingList>("SendGetRatingList");
scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::SendGetProfile>("SendGetProfile");
scriptInterface.RegisterFunction<void, CScriptVal, &JSI_Lobby::SendRegisterGame>("SendRegisterGame");
scriptInterface.RegisterFunction<void, CScriptVal, &JSI_Lobby::SendGameReport>("SendGameReport");
scriptInterface.RegisterFunction<void, &JSI_Lobby::SendUnregisterGame>("SendUnregisterGame");
@ -1034,6 +1035,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetPlayerList>("GetPlayerList");
scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetGameList>("GetGameList");
scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetBoardList>("GetBoardList");
scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetProfile>("GetProfile");
scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::LobbyGuiPollMessage>("LobbyGuiPollMessage");
scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::LobbySendMessage>("LobbySendMessage");
scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::LobbySetPlayerPresence>("LobbySetPlayerPresence");

View File

@ -34,6 +34,7 @@ public:
virtual void SendIqGetGameList() = 0;
virtual void SendIqGetBoardList() = 0;
virtual void SendIqGetRatingList() = 0;
virtual void SendIqGetProfile(const std::string& player) = 0;
virtual void SendIqGameReport(ScriptInterface& scriptInterface, CScriptVal data) = 0;
virtual void SendIqRegisterGame(ScriptInterface& scriptInterface, CScriptVal data) = 0;
virtual void SendIqUnregisterGame() = 0;
@ -50,6 +51,7 @@ public:
virtual CScriptValRooted GUIGetPlayerList(ScriptInterface& scriptInterface) = 0;
virtual CScriptValRooted GUIGetGameList(ScriptInterface& scriptInterface) = 0;
virtual CScriptValRooted GUIGetBoardList(ScriptInterface& scriptInterface) = 0;
virtual CScriptValRooted GUIGetProfile(ScriptInterface& scriptInterface) = 0;
virtual CScriptValRooted GuiPollMessage(ScriptInterface& scriptInterface) = 0;
virtual void SendMUCMessage(const std::string& message) = 0;

View File

@ -185,3 +185,67 @@ GameListQuery::~GameListQuery()
glooxwrapper::Tag::free(*it);
m_GameList.clear();
}
/******************************************************
* ProfileQuery, a custom IQ Stanza useful for fetching
* user profiles
* Example stanza:
* <profile player="foobar" highestRating="1500" rank="1895" totalGamesPlayed="50"
* wins="25" losses="25" /><command>foobar</command>
*/
ProfileQuery::ProfileQuery(const glooxwrapper::Tag* tag):StanzaExtension(ExtProfileQuery)
{
if (!tag || tag->name() != "query" || tag->xmlns() != XMLNS_PROFILE)
return;
const glooxwrapper::Tag* c = tag->findTag_clone("query/command");
if (c)
m_Command = c->cdata();
glooxwrapper::Tag::free(c);
const glooxwrapper::ConstTagList profileTags = tag->findTagList_clone("query/profile");
glooxwrapper::ConstTagList::const_iterator it = profileTags.begin();
for (; it != profileTags.end(); ++it)
m_StanzaProfile.push_back(*it);
}
/**
* Required by gloox, used to find the Profile element in a received IQ.
*/
const glooxwrapper::string& ProfileQuery::filterString() const
{
static const glooxwrapper::string filter = "/iq/query[@xmlns='" XMLNS_PROFILE "']";
return filter;
}
/**
* Required by gloox, used to serialize the Profile request into XML for sending.
*/
glooxwrapper::Tag* ProfileQuery::tag() const
{
glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("query");
t->setXmlns(XMLNS_PROFILE);
if (!m_Command.empty())
t->addChild(glooxwrapper::Tag::allocate("command", m_Command));
std::vector<const glooxwrapper::Tag*>::const_iterator it = m_StanzaProfile.begin();
for (; it != m_StanzaProfile.end(); ++it)
t->addChild((*it)->clone());
return t;
}
glooxwrapper::StanzaExtension* ProfileQuery::clone() const
{
ProfileQuery* q = new ProfileQuery();
return q;
}
ProfileQuery::~ProfileQuery()
{
std::vector<const glooxwrapper::Tag*>::const_iterator it = m_StanzaProfile.begin();
for (; it != m_StanzaProfile.end(); ++it)
glooxwrapper::Tag::free(*it);
m_StanzaProfile.clear();
}

View File

@ -31,6 +31,10 @@
#define ExtGameReport 1405
#define XMLNS_GAMEREPORT "jabber:iq:gamereport"
/// Global Profile Extension
#define ExtProfileQuery 1406
#define XMLNS_PROFILE "jabber:iq:profile"
class GameReport : public glooxwrapper::StanzaExtension
{
public:
@ -87,4 +91,24 @@ public:
glooxwrapper::string m_Command;
std::vector<const glooxwrapper::Tag*> m_StanzaBoardList;
};
class ProfileQuery : public glooxwrapper::StanzaExtension
{
public:
ProfileQuery(const glooxwrapper::Tag* tag = 0);
// Following four methods are all required by gloox
virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
{
return new ProfileQuery(tag);
}
virtual const glooxwrapper::string& filterString() const;
virtual glooxwrapper::Tag* tag() const;
virtual glooxwrapper::StanzaExtension* clone() const;
~ProfileQuery();
glooxwrapper::string m_Command;
std::vector<const glooxwrapper::Tag*> m_StanzaProfile;
};
#endif

View File

@ -102,19 +102,22 @@ XmppClient::XmppClient(const std::string& sUsername, const std::string& sPasswor
const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
m_client->setSASLMechanisms(mechs);
m_client->registerConnectionListener( this );
m_client->registerConnectionListener(this);
m_client->setPresence(gloox::Presence::Available, -1);
m_client->disco()->setVersion( "Pyrogenesis", "0.0.17" );
m_client->disco()->setIdentity( "client", "bot" );
m_client->disco()->setVersion("Pyrogenesis", "0.0.17");
m_client->disco()->setIdentity("client", "bot");
m_client->setCompression(false);
m_client->registerStanzaExtension( new GameListQuery() );
m_client->registerIqHandler( this, ExtGameListQuery);
m_client->registerStanzaExtension(new GameListQuery());
m_client->registerIqHandler(this, ExtGameListQuery);
m_client->registerStanzaExtension( new BoardListQuery() );
m_client->registerIqHandler( this, ExtBoardListQuery);
m_client->registerStanzaExtension(new BoardListQuery());
m_client->registerIqHandler(this, ExtBoardListQuery);
m_client->registerMessageHandler( this );
m_client->registerStanzaExtension(new ProfileQuery());
m_client->registerIqHandler(this, ExtProfileQuery);
m_client->registerMessageHandler(this);
// Uncomment to see the raw stanzas
//m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
@ -152,6 +155,8 @@ XmppClient::~XmppClient()
glooxwrapper::Tag::free(*it);
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
glooxwrapper::Tag::free(*it);
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
glooxwrapper::Tag::free(*it);
}
/// Network
@ -212,9 +217,12 @@ void XmppClient::onDisconnect(gloox::ConnectionError error)
glooxwrapper::Tag::free(*it);
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
glooxwrapper::Tag::free(*it);
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
glooxwrapper::Tag::free(*it);
m_BoardList.clear();
m_GameList.clear();
m_PlayerMap.clear();
m_Profile.clear();
if(error == gloox::ConnAuthenticationFailed)
CreateSimpleMessage("system", "authentication failed", "error");
@ -283,6 +291,22 @@ void XmppClient::SendIqGetBoardList()
m_client->send(iq);
}
/**
* Request the profile data from the server.
*/
void XmppClient::SendIqGetProfile(const std::string& player)
{
glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
// Send IQ
ProfileQuery* b = new ProfileQuery();
b->m_Command = player;
glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
iq.addExtension(b);
DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
m_client->send(iq);
}
/**
* Request the rating data from the server.
*/
@ -518,8 +542,7 @@ CScriptValRooted XmppClient::GUIGetGameList(ScriptInterface& scriptInterface)
scriptInterface.Eval("({})", &game);
const char* stats[] = { "name", "ip", "state", "nbp", "tnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition" };
short stats_length = 11;
for (short i = 0; i < stats_length; i++)
for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
scriptInterface.SetProperty(game, stats[i], wstring_from_utf8((*it)->findAttribute(stats[i]).to_string()));
scriptInterface.CallFunctionVoid(gameList, "push", game);
@ -546,8 +569,7 @@ CScriptValRooted XmppClient::GUIGetBoardList(ScriptInterface& scriptInterface)
scriptInterface.Eval("({})", &board);
const char* attributes[] = { "name", "rank", "rating" };
short attributes_length = 3;
for (short i = 0; i < attributes_length; i++)
for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i)
scriptInterface.SetProperty(board, attributes[i], wstring_from_utf8((*it)->findAttribute(attributes[i]).to_string()));
scriptInterface.CallFunctionVoid(boardList, "push", board);
@ -556,6 +578,33 @@ CScriptValRooted XmppClient::GUIGetBoardList(ScriptInterface& scriptInterface)
return CScriptValRooted(cx, boardList);
}
/**
* Handle requests from the GUI for profile data.
*
* @return A JS array containing the specific user's profile data
*/
CScriptValRooted XmppClient::GUIGetProfile(ScriptInterface& scriptInterface)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue profileFetch(cx);
scriptInterface.Eval("([])", &profileFetch);
const char* stats[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" };
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
{
JS::RootedValue profile(cx);
scriptInterface.Eval("({})", &profile);
for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
scriptInterface.SetProperty(profile, stats[i], wstring_from_utf8((*it)->findAttribute(stats[i]).to_string()));
scriptInterface.CallFunctionVoid(profileFetch, "push", profile);
}
return CScriptValRooted(cx, profileFetch);
}
/*****************************************************
* Message interfaces *
*****************************************************/
@ -648,6 +697,7 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
{
const GameListQuery* gq = iq.findExtension<GameListQuery>( ExtGameListQuery );
const BoardListQuery* bq = iq.findExtension<BoardListQuery>( ExtBoardListQuery );
const ProfileQuery* pq = iq.findExtension<ProfileQuery>( ExtProfileQuery );
if(gq)
{
for(std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it )
@ -684,8 +734,19 @@ bool XmppClient::handleIq(const glooxwrapper::IQ& iq)
CreateSimpleMessage("system", "ratinglist updated", "internal");
}
}
if (pq)
{
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
glooxwrapper::Tag::free(*it);
m_Profile.clear();
for (std::vector<const glooxwrapper::Tag*>::const_iterator it = pq->m_StanzaProfile.begin(); it != pq->m_StanzaProfile.end(); ++it)
m_Profile.push_back((*it)->clone());
CreateSimpleMessage("system", "profile updated", "internal");
}
}
else if(iq.subtype() == gloox::IQ::Error)
else if (iq.subtype() == gloox::IQ::Error)
{
gloox::StanzaError err = iq.error_error();
std::string msg = StanzaErrorToString(err);

View File

@ -61,6 +61,7 @@ public:
void SendIqGetGameList();
void SendIqGetBoardList();
void SendIqGetRatingList();
void SendIqGetProfile(const std::string& player);
void SendIqGameReport(ScriptInterface& scriptInterface, CScriptVal data);
void SendIqRegisterGame(ScriptInterface& scriptInterface, CScriptVal data);
void SendIqUnregisterGame();
@ -77,6 +78,7 @@ public:
CScriptValRooted GUIGetPlayerList(ScriptInterface& scriptInterface);
CScriptValRooted GUIGetGameList(ScriptInterface& scriptInterface);
CScriptValRooted GUIGetBoardList(ScriptInterface& scriptInterface);
CScriptValRooted GUIGetProfile(ScriptInterface& scriptInterface);
//Script
ScriptInterface& GetScriptInterface();
@ -143,6 +145,8 @@ private:
std::vector<const glooxwrapper::Tag*> m_GameList;
/// List of rankings
std::vector<const glooxwrapper::Tag*> m_BoardList;
/// Profile data
std::vector<const glooxwrapper::Tag*> m_Profile;
/// Queue of messages for the GUI
std::deque<GUIMessage> m_GuiMessageQueue;
/// Current room subject/topic.

View File

@ -101,6 +101,13 @@ void JSI_Lobby::SendGetRatingList(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)
g_XmppClient->SendIqGetRatingList();
}
void JSI_Lobby::SendGetProfile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring player)
{
if (!g_XmppClient)
return;
g_XmppClient->SendIqGetProfile(utf8_from_wstring(player));
}
void JSI_Lobby::SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal data)
{
if (!g_XmppClient)
@ -161,6 +168,16 @@ CScriptVal JSI_Lobby::GetBoardList(ScriptInterface::CxPrivate* pCxPrivate)
return boardList.get();
}
CScriptVal JSI_Lobby::GetProfile(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_XmppClient)
return CScriptVal();
CScriptValRooted profileFetch = g_XmppClient->GUIGetProfile(*(pCxPrivate->pScriptInterface));
return profileFetch.get();
}
CScriptVal JSI_Lobby::LobbyGuiPollMessage(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_XmppClient)

View File

@ -37,6 +37,7 @@ namespace JSI_Lobby
void SendGetGameList(ScriptInterface::CxPrivate* pCxPrivate);
void SendGetBoardList(ScriptInterface::CxPrivate* pCxPrivate);
void SendGetRatingList(ScriptInterface::CxPrivate* pCxPrivate);
void SendGetProfile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring player);
void SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal data);
void SendRegisterGame(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal data);
void SendUnregisterGame(ScriptInterface::CxPrivate* pCxPrivate);
@ -44,6 +45,7 @@ namespace JSI_Lobby
CScriptVal GetPlayerList(ScriptInterface::CxPrivate* pCxPrivate);
CScriptVal GetGameList(ScriptInterface::CxPrivate* pCxPrivate);
CScriptVal GetBoardList(ScriptInterface::CxPrivate* pCxPrivate);
CScriptVal GetProfile(ScriptInterface::CxPrivate* pCxPrivate);
CScriptVal LobbyGuiPollMessage(ScriptInterface::CxPrivate* pCxPrivate);
void LobbySendMessage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring message);
void LobbySetPlayerPresence(ScriptInterface::CxPrivate* pCxPrivate, std::wstring presence);

View File

@ -33,6 +33,7 @@ class Player(Base):
id = Column(Integer, primary_key=True)
jid = Column(String(255))
rating = Column(Integer)
highest_rating = Column(Integer)
games = relationship('Game', secondary='players_info')
# These two relations really only exist to satisfy the linkage
# between PlayerInfo and Player and Game and player.

View File

@ -26,6 +26,8 @@ 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
@ -37,6 +39,34 @@ 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).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.
@ -180,6 +210,14 @@ class LeaderboardList():
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
@ -426,6 +464,22 @@ class GameReportXmppPlugin(ElementBase):
data[key] = item
return data
## Class for custom profile ##
class ProfileXmppPlugin(ElementBase):
name = 'query'
namespace = 'jabber:iq:profile'
interfaces = set(('profile', 'command'))
sub_interfaces = interfaces
plugin_attrib = 'profile'
def addCommand(self, command):
commandXml = ET.fromstring("<command>%s</command>" % command)
self.xml.append(commandXml)
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,
"losses" : losses})
self.xml.append(itemXml)
## Main class which handles IQ data and sends new data ##
class XpartaMuPP(sleekxmpp.ClientXMPP):
"""
@ -454,6 +508,7 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
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 Gamelist',
StanzaPath('iq/gamelist'),
@ -467,6 +522,11 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
StanzaPath('iq/gamereport'),
self.iqhandler,
instream=True))
self.register_handler(Callback('Iq Profile',
StanzaPath('iq/profile'),
self.iqhandler,
instream=True))
self.add_event_handler("session_start", self.start)
self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online)
@ -566,6 +626,15 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
logging.error("Failed to process ratinglist request from %s" % iq['from'].bare)
else:
logging.error("Failed to process boardlist request from %s" % iq['from'].bare)
elif 'profile' in iq.plugins:
command = iq['profile']['command']
try:
self.sendProfile(iq['from'], command)
except:
try:
self.sendProfileNotFound(iq['from'], command)
except:
logging.debug("No record found for %s" % command)
else:
logging.error("Unknown 'get' type stanza request from %s" % iq['from'].bare)
elif iq['type'] == 'result':
@ -748,6 +817,73 @@ class XpartaMuPP(sleekxmpp.ClientXMPP):
except:
logging.error("Failed to send rating list")
def sendProfile(self, to, player):
"""
Send the profile to a specified player.
"""
if to == "":
logging.error("Failed to send profile")
return
online = False;
## Pull stats and add it to the stanza
for JID in self.nicks.keys():
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)
## 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")
def sendProfileNotFound(self, to, player):
"""
Send a profile not-found error to a specified player.
"""
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")
## Main Program ##
if __name__ == '__main__':
# Setup the command line arguments.