Ticket #2504: profile3.patch

File profile3.patch, 38.6 KB (added by scythetwirler, 10 years ago)
  • binaries/data/mods/public/gui/lobby/lobby.js

     
    191191}
    192192
    193193/**
     194 * Display the profile of the selected player.
     195 * Displays N/A for all stats until updateProfile is called when the stats
     196 *  are actually received from the bot.
     197 *
     198 * @param caller From which screen is the user requesting data from?
     199 */
     200function displayProfile(caller)
     201{
     202    var playerList, rating;
     203    if (caller == "leaderboard")
     204        playerList = Engine.GetGUIObjectByName("leaderboardBox");
     205    else if (caller == "lobbylist")
     206        playerList = Engine.GetGUIObjectByName("playersBox");
     207    else if (caller == "fetch")
     208    {
     209        Engine.SendGetProfile(Engine.GetGUIObjectByName("fetchInput").caption);
     210        return;
     211    }
     212    else
     213        return;
     214
     215    if (!playerList.list[playerList.selected])
     216    {
     217        Engine.GetGUIObjectByName("profileArea").hidden = true;
     218        return;
     219    }
     220    Engine.GetGUIObjectByName("profileArea").hidden = false;
     221
     222    Engine.SendGetProfile(playerList.list[playerList.selected]);   
     223
     224    var user = playerList.list_name[playerList.selected];
     225    var role = Engine.LobbyGetPlayerRole(playerList.list[playerList.selected]);
     226    var userList = Engine.GetGUIObjectByName("playersBox");
     227    if (role && caller == "lobbylist")
     228    {
     229        // Make the role uppercase.
     230        role = role.charAt(0).toUpperCase() + role.slice(1);
     231        if (role == "Moderator")
     232            role = '[color="0 125 0"]' + translate(role) + '[/color]';
     233    }
     234    else
     235        role = "";
     236
     237    Engine.GetGUIObjectByName("usernameText").caption = user;
     238    Engine.GetGUIObjectByName("roleText").caption = translate(role);
     239    Engine.GetGUIObjectByName("rankText").caption = translate("N/A");
     240    Engine.GetGUIObjectByName("highestRatingText").caption = translate("N/A");
     241    Engine.GetGUIObjectByName("totalGamesText").caption = translate("N/A");
     242    Engine.GetGUIObjectByName("winsText").caption = translate("N/A");
     243    Engine.GetGUIObjectByName("lossesText").caption = translate("N/A");
     244    Engine.GetGUIObjectByName("ratioText").caption = translate("N/A");
     245}
     246
     247/**
     248 * Update the profile of the selected player with data from the bot.
     249 *
     250 */
     251function updateProfile()
     252{
     253    var playerList, user;
     254    var attributes = Engine.GetProfile();
     255
     256    if (!Engine.GetGUIObjectByName("profileFetch").hidden)
     257    {
     258        user = attributes[0].player;
     259        if (attributes[0].rating == "-2") // Profile not found code
     260        {
     261            Engine.GetGUIObjectByName("profileWindowArea").hidden = true;
     262            Engine.GetGUIObjectByName("profileErrorText").hidden = false;
     263            return;
     264        }
     265        Engine.GetGUIObjectByName("profileWindowArea").hidden = false;
     266        Engine.GetGUIObjectByName("profileErrorText").hidden = true;
     267       
     268        if (attributes[0].rating != "")
     269            user = sprintf(translate("%(nick)s (%(rating)s)"), { nick: user, rating: attributes[0].rating });
     270
     271        Engine.GetGUIObjectByName("profileUsernameText").caption = user;
     272        Engine.GetGUIObjectByName("profileRankText").caption = attributes[0].rank;
     273        Engine.GetGUIObjectByName("profileHighestRatingText").caption = attributes[0].highestRating;
     274        Engine.GetGUIObjectByName("profileTotalGamesText").caption = attributes[0].totalGamesPlayed;
     275        Engine.GetGUIObjectByName("profileWinsText").caption = attributes[0].wins;
     276        Engine.GetGUIObjectByName("profileLossesText").caption = attributes[0].losses;
     277
     278        var winRate = (attributes[0].wins / attributes[0].totalGamesPlayed * 100).toFixed(2);
     279        if (attributes[0].totalGamesPlayed != 0)
     280            Engine.GetGUIObjectByName("profileRatioText").caption = sprintf(translate("%(percentage)s%%"), { percentage: winRate });
     281        else
     282            Engine.GetGUIObjectByName("profileRatioText").caption = translate("-");
     283        return;
     284    }
     285    else if (!Engine.GetGUIObjectByName("leaderboard").hidden)
     286        playerList = Engine.GetGUIObjectByName("leaderboardBox");
     287    else
     288        playerList = Engine.GetGUIObjectByName("playersBox");
     289   
     290    if (attributes[0].rating == "-2")
     291        return;
     292    // Make sure the stats we have received coincide with the selected player.
     293    if (attributes[0].player != playerList.list[playerList.selected])
     294        return;
     295    user = playerList.list_name[playerList.selected];
     296    if (attributes[0].rating != "")
     297        user = sprintf(translate("%(nick)s (%(rating)s)"), { nick: user, rating: attributes[0].rating });
     298
     299    Engine.GetGUIObjectByName("usernameText").caption = user;
     300    Engine.GetGUIObjectByName("rankText").caption = attributes[0].rank;
     301    Engine.GetGUIObjectByName("highestRatingText").caption = attributes[0].highestRating;
     302    Engine.GetGUIObjectByName("totalGamesText").caption = attributes[0].totalGamesPlayed;
     303    Engine.GetGUIObjectByName("winsText").caption = attributes[0].wins;
     304    Engine.GetGUIObjectByName("lossesText").caption = attributes[0].losses;
     305
     306    var winRate = (attributes[0].wins / attributes[0].totalGamesPlayed * 100).toFixed(2);
     307    if (attributes[0].totalGamesPlayed != 0)
     308        Engine.GetGUIObjectByName("ratioText").caption = sprintf(translate("%(percentage)s%%"), { percentage: winRate });
     309    else
     310        Engine.GetGUIObjectByName("ratioText").caption = translate("-");
     311}
     312
     313/**
    194314 * Update the leaderboard from data cached in C++.
    195315 */
    196316function updateLeaderboard()
     
    592712                case "ratinglist updated":
    593713                    updatePlayerList();
    594714                    break;
     715                case "profile updated":
     716                    updateProfile();
     717                    break;
    595718                }
    596                 break
     719                break;
    597720            }
    598721            break;
    599722        default:
  • binaries/data/mods/public/gui/lobby/lobby.xml

     
    1919        </action>
    2020
    2121        <!-- Left panel: Player list. -->
    22         <object name="leftPanel" size="20 30 20% 100%-50">
     22        <object name="leftPanel" size="20 30 20% 100%-280">
    2323            <object name="playersBox" style="ModernList" type="olist" size="0 0 100% 100%" font="sans-bold-stroke-13">
    2424                <def id="status" width="26%">
    2525                    <translatableAttribute id="heading">Status</translatableAttribute>
     
    3030                <def id="rating" width="24%">
    3131                    <translatableAttribute id="heading">Rating</translatableAttribute>
    3232                </def>
     33                <action on="SelectionChange">
     34                    displayProfile("lobbylist");
     35                </action>
    3336            </object>
    3437        </object>
    3538
    36         <object name="leftButtonPanel" size="20 100%-45 20% 100%-20">
    37             <object type="button" style="ModernButtonRed" size="0 0 100% 100%">
     39        <object name="profilePanel" size="20 100%-275 20% 100%-80">
     40            <object name="profileBox" type="image" sprite="ModernDarkBoxGold" size="0 0 100% 100%">
     41                <object name="profileArea" size="0 0 100% 100%" hidden="true">
     42                    <object name="usernameText" size="0 0 100% 45" type="text" style="ModernLabelText" text_align="center" font="sans-bold-16" />
     43                    <object name="roleText" size="0 45 100% 70" type="text" style="ModernLabelText" text_align="center" font="sans-bold-stroke-12" />
     44                    <object size="0 70 40%+40 90" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     45                        <translatableAttribute id="caption">Current Rank:</translatableAttribute>
     46                    </object>
     47                    <object name="rankText" size="40%+45 70 100% 90" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     48                    <object size="0 90 40%+40 110" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     49                        <translatableAttribute id="caption">Highest Rating:</translatableAttribute>
     50                    </object>
     51                    <object name="highestRatingText" size="40%+45 90 100% 110" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     52                    <object size="0 110 40%+40 130" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     53                        <translatableAttribute id="caption">Total Games:</translatableAttribute>
     54                    </object>
     55                    <object name="totalGamesText" size="40%+45 110 100% 130" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     56                    <object size="0 130 40%+40 150" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     57                        <translatableAttribute id="caption">Wins:</translatableAttribute>
     58                    </object>
     59                    <object name="winsText" size="40%+45 130 100% 150" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     60                    <object size="0 150 40%+40 170" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     61                        <translatableAttribute id="caption">Losses:</translatableAttribute>
     62                    </object>
     63                    <object name="lossesText" size="40%+45 150 100% 170" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     64                    <object size="0 170 40%+40 190" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     65                        <translatableAttribute id="caption">Win Rate:</translatableAttribute>
     66                    </object>
     67                    <object name="ratioText" size="40%+45 170 100% 190" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     68                </object>
     69            </object>
     70        </object>
     71
     72        <object name="leftButtonPanel" size="20 100%-75 20% 100%-20">
     73            <object type="button" style="ModernButtonRed" size="0 0 100% 25">
    3874                <translatableAttribute id="caption">Leaderboard</translatableAttribute>
    39                 <action on="Press">Engine.GetGUIObjectByName("leaderboard").hidden = false;Engine.GetGUIObjectByName("leaderboardFade").hidden = false;</action>
     75                <action on="Press">
     76                    Engine.GetGUIObjectByName("leaderboard").hidden = false;
     77                    Engine.GetGUIObjectByName("fade").hidden = false;
     78                    displayProfile("leaderboard");
     79                </action>
    4080            </object>
     81            <object type="button" style="ModernButtonRed" size="0 30 100% 100%">
     82                <translatableAttribute id="caption">User Profile Lookup</translatableAttribute>
     83                <action on="Press">
     84                    Engine.GetGUIObjectByName("profileFetch").hidden = false;
     85                    Engine.GetGUIObjectByName("fade").hidden = false;
     86                </action>
     87            </object>
    4188        </object>
    4289
    4390        <!-- Right panel: Game details. -->
     
    191238
    192239        <!-- START Window for leaderboard stats -->
    193240        <!-- Add a translucent black background to fade out the menu page -->
    194         <object hidden="true" name="leaderboardFade" type="image" z="100" sprite="ModernFade"/>
     241        <object hidden="true" name="fade" type="image" z="100" sprite="ModernFade"/>
    195242        <object hidden="true" name="leaderboard" type="image" style="ModernDialog" size="50%-224 50%-160 50%+224 50%+160" z="101">
    196243            <object style="ModernLabelText" type="text" size="50%-128 -18 50%+128 14">
    197244                <translatableAttribute id="caption">Leaderboard</translatableAttribute>
     
    209256                <def id="rating" color="255 255 255" width="30%">
    210257                    <translatableAttribute id="heading">Rating</translatableAttribute>
    211258                </def>
     259                <action on="SelectionChange">
     260                    displayProfile("leaderboard");
     261                </action>
    212262            </object>
    213263            <object type="button" style="ModernButtonRed" size="50%-133 100%-45 50%-5 100%-17">
    214264                <translatableAttribute id="caption">Back</translatableAttribute>
    215                 <action on="Press">Engine.GetGUIObjectByName("leaderboard").hidden = true;Engine.GetGUIObjectByName("leaderboardFade").hidden = true;</action>
     265                <action on="Press">
     266                    Engine.GetGUIObjectByName("leaderboard").hidden = true;
     267                    Engine.GetGUIObjectByName("fade").hidden = true;
     268                    displayProfile("lobbylist");
     269                </action>
    216270            </object>
    217271            <object type="button" style="ModernButtonRed" size="50%+5 100%-45 50%+133 100%-17">
    218272                <translatableAttribute id="caption">Update</translatableAttribute>
     
    220274            </object>
    221275        </object>
    222276        <!-- END Window for leaderboard stats -->
     277        <object hidden="true" name="profileFetch" type="image" style="ModernDialog" size="50%-224 50%-160 50%+224 50%+160" z="102">
     278            <object style="ModernLabelText" type="text" size="50%-128 -18 50%+128 14">
     279                <translatableAttribute id="caption">User Profile Lookup</translatableAttribute>
     280            </object>
     281            <object type="text" size="15 25 40% 50" text_align="right" textcolor="white">
     282                    <translatableAttribute id="caption">Enter username:</translatableAttribute>
     283            </object>
     284            <object name="fetchInput" size="40%+10 25 100%-25 50" type="input" style="ModernInput" font="sans-13">
     285                <action on="Press">displayProfile("fetch");</action>
     286            </object>
     287            <object type="button" style="ModernButtonRed" size="50%-64 60 50%+64 85">
     288                <translatableAttribute id="caption">View Profile</translatableAttribute>
     289                <action on="Press">displayProfile("fetch");</action>
     290            </object>
     291            <object name="profileWindowPanel" size="25 95 100%-25 100%-60">
     292            <object name="profileWindowBox" type="image" sprite="ModernDarkBoxGold" size="0 0 100% 100%">
     293                <object name="profileWindowArea" size="0 0 100% 100%" hidden="false">
     294                    <object name="profileUsernameText" size="0 0 100% 25" type="text" style="ModernLabelText" text_align="center" font="sans-bold-16" />
     295                    <object size="0 30 40%+40 50" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     296                        <translatableAttribute id="caption">Current Rank:</translatableAttribute>
     297                    </object>
     298                    <object name="profileRankText" size="40%+45 30 100% 50" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     299                    <object size="0 50 40%+40 70" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     300                        <translatableAttribute id="caption">Highest Rating:</translatableAttribute>
     301                    </object>
     302                    <object name="profileHighestRatingText" size="40%+45 50 100% 70" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     303                    <object size="0 70 40%+40 90" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     304                        <translatableAttribute id="caption">Total Games:</translatableAttribute>
     305                    </object>
     306                    <object name="profileTotalGamesText" size="40%+45 70 100% 90" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     307                    <object size="0 90 40%+40 110" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     308                        <translatableAttribute id="caption">Wins:</translatableAttribute>
     309                    </object>
     310                    <object name="profileWinsText" size="40%+45 90 100% 110" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     311                    <object size="0 110 40%+40 130" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     312                        <translatableAttribute id="caption">Losses:</translatableAttribute>
     313                    </object>
     314                    <object name="profileLossesText" size="40%+45 110 100% 130" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     315                    <object size="0 130 40%+40 150" type="text" style="ModernLabelText" text_align="right" font="sans-bold-stroke-13">
     316                        <translatableAttribute id="caption">Win Rate:</translatableAttribute>
     317                    </object>
     318                    <object name="profileRatioText" size="40%+45 130 100% 150" type="text" style="ModernLabelText" text_align="left" font="sans-bold-stroke-12" />
     319                </object>
     320                <object name="profileErrorText" size="25% 25% 75% 75%" type="text" style="ModernLabelText" text_align="center" font="sans-bold-stroke-13" hidden="true">
     321                    <translatableAttribute id="caption">Player not found.</translatableAttribute>
     322                </object>
     323            </object>           
     324            </object>
     325            <object type="button" style="ModernButtonRed" size="50%-64 100%-50 50%+64 100%-25">
     326                <translatableAttribute id="caption">Back</translatableAttribute>
     327                <action on="Press">
     328                    Engine.GetGUIObjectByName("profileFetch").hidden = true;
     329                    Engine.GetGUIObjectByName("fade").hidden = true;
     330                </action>
     331            </object>
     332        </object>
    223333    </object>
    224334</objects>
  • source/gui/scripting/ScriptFunctions.cpp

     
    10271027    scriptInterface.RegisterFunction<void, &JSI_Lobby::SendGetGameList>("SendGetGameList");
    10281028    scriptInterface.RegisterFunction<void, &JSI_Lobby::SendGetBoardList>("SendGetBoardList");
    10291029    scriptInterface.RegisterFunction<void, &JSI_Lobby::SendGetRatingList>("SendGetRatingList");
     1030    scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::SendGetProfile>("SendGetProfile");
    10301031    scriptInterface.RegisterFunction<void, CScriptVal, &JSI_Lobby::SendRegisterGame>("SendRegisterGame");
    10311032    scriptInterface.RegisterFunction<void, CScriptVal, &JSI_Lobby::SendGameReport>("SendGameReport");
    10321033    scriptInterface.RegisterFunction<void, &JSI_Lobby::SendUnregisterGame>("SendUnregisterGame");
     
    10341035    scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetPlayerList>("GetPlayerList");
    10351036    scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetGameList>("GetGameList");
    10361037    scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetBoardList>("GetBoardList");
     1038    scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::GetProfile>("GetProfile");
    10371039    scriptInterface.RegisterFunction<CScriptVal, &JSI_Lobby::LobbyGuiPollMessage>("LobbyGuiPollMessage");
    10381040    scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::LobbySendMessage>("LobbySendMessage");
    10391041    scriptInterface.RegisterFunction<void, std::wstring, &JSI_Lobby::LobbySetPlayerPresence>("LobbySetPlayerPresence");
  • source/lobby/IXmppClient.h

     
    3434    virtual void SendIqGetGameList() = 0;
    3535    virtual void SendIqGetBoardList() = 0;
    3636    virtual void SendIqGetRatingList() = 0;
     37    virtual void SendIqGetProfile(const std::string& player) = 0;
    3738    virtual void SendIqGameReport(ScriptInterface& scriptInterface, CScriptVal data) = 0;
    3839    virtual void SendIqRegisterGame(ScriptInterface& scriptInterface, CScriptVal data) = 0;
    3940    virtual void SendIqUnregisterGame() = 0;
     
    5051    virtual CScriptValRooted GUIGetPlayerList(ScriptInterface& scriptInterface) = 0;
    5152    virtual CScriptValRooted GUIGetGameList(ScriptInterface& scriptInterface) = 0;
    5253    virtual CScriptValRooted GUIGetBoardList(ScriptInterface& scriptInterface) = 0;
     54    virtual CScriptValRooted GUIGetProfile(ScriptInterface& scriptInterface) = 0;
    5355
    5456    virtual CScriptValRooted GuiPollMessage(ScriptInterface& scriptInterface) = 0;
    5557    virtual void SendMUCMessage(const std::string& message) = 0;
  • source/lobby/StanzaExtensions.cpp

     
    185185        glooxwrapper::Tag::free(*it);
    186186    m_GameList.clear();
    187187}
     188
     189/******************************************************
     190 * ProfileQuery, a custom IQ Stanza useful for fetching
     191 * user profiles
     192 * Example stanza:
     193 * <profile player="foobar" highestRating="1500" rank="1895" totalGamesPlayed="50"
     194 *  wins="25" losses="25" /><command>foobar</command>
     195 */
     196ProfileQuery::ProfileQuery(const glooxwrapper::Tag* tag):StanzaExtension(ExtProfileQuery)
     197{
     198    if (!tag || tag->name() != "query" || tag->xmlns() != XMLNS_PROFILE)
     199        return;
     200
     201    const glooxwrapper::Tag* c = tag->findTag_clone("query/command");
     202    if (c)
     203        m_Command = c->cdata();
     204    glooxwrapper::Tag::free(c);
     205
     206    const glooxwrapper::ConstTagList profileTags = tag->findTagList_clone("query/profile");
     207    glooxwrapper::ConstTagList::const_iterator it = profileTags.begin();
     208    for (; it != profileTags.end(); ++it)
     209        m_StanzaProfile.push_back(*it);
     210}
     211
     212/**
     213 * Required by gloox, used to find the Profile element in a received IQ.
     214 */
     215const glooxwrapper::string& ProfileQuery::filterString() const
     216{
     217    static const glooxwrapper::string filter = "/iq/query[@xmlns='" XMLNS_PROFILE "']";
     218    return filter;
     219}
     220
     221/**
     222 * Required by gloox, used to serialize the Profile request into XML for sending.
     223 */
     224glooxwrapper::Tag* ProfileQuery::tag() const
     225{
     226    glooxwrapper::Tag* t = glooxwrapper::Tag::allocate("query");
     227    t->setXmlns(XMLNS_PROFILE);
     228
     229    if (!m_Command.empty())
     230        t->addChild(glooxwrapper::Tag::allocate("command", m_Command));
     231
     232    std::vector<const glooxwrapper::Tag*>::const_iterator it = m_StanzaProfile.begin();
     233    for (; it != m_StanzaProfile.end(); ++it)
     234        t->addChild((*it)->clone());
     235
     236    return t;
     237}
     238
     239glooxwrapper::StanzaExtension* ProfileQuery::clone() const
     240{
     241    ProfileQuery* q = new ProfileQuery();
     242    return q;
     243}
     244
     245ProfileQuery::~ProfileQuery()
     246{
     247    std::vector<const glooxwrapper::Tag*>::const_iterator it = m_StanzaProfile.begin();
     248    for (; it != m_StanzaProfile.end(); ++it)
     249        glooxwrapper::Tag::free(*it);
     250    m_StanzaProfile.clear();
     251}
  • source/lobby/StanzaExtensions.h

     
    3131#define ExtGameReport 1405
    3232#define XMLNS_GAMEREPORT "jabber:iq:gamereport"
    3333
     34/// Global Profile Extension
     35#define ExtProfileQuery 1406
     36#define XMLNS_PROFILE "jabber:iq:profile"
     37
    3438class GameReport : public glooxwrapper::StanzaExtension
    3539{
    3640public:
     
    8791    glooxwrapper::string m_Command;
    8892    std::vector<const glooxwrapper::Tag*> m_StanzaBoardList;
    8993};
     94
     95class ProfileQuery : public glooxwrapper::StanzaExtension
     96{
     97public:
     98    ProfileQuery(const glooxwrapper::Tag* tag = 0);
     99
     100    // Following four methods are all required by gloox
     101    virtual StanzaExtension* newInstance(const glooxwrapper::Tag* tag) const
     102    {
     103        return new ProfileQuery(tag);
     104    }
     105    virtual const glooxwrapper::string& filterString() const;
     106    virtual glooxwrapper::Tag* tag() const;
     107    virtual glooxwrapper::StanzaExtension* clone() const;
     108
     109    ~ProfileQuery();
     110
     111    glooxwrapper::string m_Command;
     112    std::vector<const glooxwrapper::Tag*> m_StanzaProfile;
     113};
    90114#endif
  • source/lobby/XmppClient.cpp

     
    102102    const int mechs = gloox::SaslMechAll ^ gloox::SaslMechPlain;
    103103    m_client->setSASLMechanisms(mechs);
    104104
    105     m_client->registerConnectionListener( this );
     105    m_client->registerConnectionListener(this);
    106106    m_client->setPresence(gloox::Presence::Available, -1);
    107     m_client->disco()->setVersion( "Pyrogenesis", "0.0.17" );
    108     m_client->disco()->setIdentity( "client", "bot" );
     107    m_client->disco()->setVersion("Pyrogenesis", "0.0.17");
     108    m_client->disco()->setIdentity("client", "bot");
    109109    m_client->setCompression(false);
    110110
    111     m_client->registerStanzaExtension( new GameListQuery() );
    112     m_client->registerIqHandler( this, ExtGameListQuery);
     111    m_client->registerStanzaExtension(new GameListQuery());
     112    m_client->registerIqHandler(this, ExtGameListQuery);
    113113
    114     m_client->registerStanzaExtension( new BoardListQuery() );
    115     m_client->registerIqHandler( this, ExtBoardListQuery);
     114    m_client->registerStanzaExtension(new BoardListQuery());
     115    m_client->registerIqHandler(this, ExtBoardListQuery);
    116116
    117     m_client->registerMessageHandler( this );
     117    m_client->registerStanzaExtension(new ProfileQuery());
     118    m_client->registerIqHandler(this, ExtProfileQuery);
    118119
     120    m_client->registerMessageHandler(this);
     121
    119122    // Uncomment to see the raw stanzas
    120123    //m_client->getWrapped()->logInstance().registerLogHandler( gloox::LogLevelDebug, gloox::LogAreaAll, this );
    121124
     
    152155        glooxwrapper::Tag::free(*it);
    153156    for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
    154157        glooxwrapper::Tag::free(*it);
     158    for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
     159        glooxwrapper::Tag::free(*it);
    155160}
    156161
    157162/// Network
     
    212217        glooxwrapper::Tag::free(*it);
    213218    for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_BoardList.begin(); it != m_BoardList.end(); ++it)
    214219        glooxwrapper::Tag::free(*it);
     220    for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
     221        glooxwrapper::Tag::free(*it);
    215222    m_BoardList.clear();
    216223    m_GameList.clear();
    217224    m_PlayerMap.clear();
     225    m_Profile.clear();
    218226
    219227    if(error == gloox::ConnAuthenticationFailed)
    220228        CreateSimpleMessage("system", "authentication failed", "error");
     
    284292}
    285293
    286294/**
     295 * Request the profile data from the server.
     296 */
     297void XmppClient::SendIqGetProfile(const std::string& player)
     298{
     299    glooxwrapper::JID xpartamuppJid(m_xpartamuppId);
     300
     301    // Send IQ
     302    ProfileQuery* b = new ProfileQuery();
     303    b->m_Command = player;
     304    glooxwrapper::IQ iq(gloox::IQ::Get, xpartamuppJid);
     305    iq.addExtension(b);
     306    DbgXMPP("SendIqGetProfile [" << tag_xml(iq) << "]");
     307    m_client->send(iq);
     308}
     309
     310/**
    287311 * Request the rating data from the server.
    288312 */
    289313void XmppClient::SendIqGetRatingList()
     
    518542        scriptInterface.Eval("({})", &game);
    519543
    520544        const char* stats[] = { "name", "ip", "state", "nbp", "tnbp", "players", "mapName", "niceMapName", "mapSize", "mapType", "victoryCondition" };
    521         short stats_length = 11;
    522         for (short i = 0; i < stats_length; i++)
     545        for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
    523546            scriptInterface.SetProperty(game, stats[i], wstring_from_utf8((*it)->findAttribute(stats[i]).to_string()));
    524547
    525548        scriptInterface.CallFunctionVoid(gameList, "push", game);
     
    546569        scriptInterface.Eval("({})", &board);
    547570
    548571        const char* attributes[] = { "name", "rank", "rating" };
    549         short attributes_length = 3;
    550         for (short i = 0; i < attributes_length; i++)
     572        for (size_t i = 0; i < ARRAY_SIZE(attributes); ++i)
    551573            scriptInterface.SetProperty(board, attributes[i], wstring_from_utf8((*it)->findAttribute(attributes[i]).to_string()));
    552574
    553575        scriptInterface.CallFunctionVoid(boardList, "push", board);
     
    556578    return CScriptValRooted(cx, boardList);
    557579}
    558580
     581/**
     582 * Handle requests from the GUI for profile data.
     583 *
     584 * @return A JS array containing the specific user's profile data
     585 */
     586CScriptValRooted XmppClient::GUIGetProfile(ScriptInterface& scriptInterface)
     587{
     588    JSContext* cx = scriptInterface.GetContext();
     589    JSAutoRequest rq(cx);
     590
     591    JS::RootedValue profileFetch(cx);
     592    scriptInterface.Eval("([])", &profileFetch);
     593    const char* stats[] = { "player", "rating", "totalGamesPlayed", "highestRating", "wins", "losses", "rank" };
     594    for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
     595    {
     596        JS::RootedValue profile(cx);
     597        scriptInterface.Eval("({})", &profile);
     598
     599        for (size_t i = 0; i < ARRAY_SIZE(stats); ++i)
     600            scriptInterface.SetProperty(profile, stats[i], wstring_from_utf8((*it)->findAttribute(stats[i]).to_string()));
     601
     602        scriptInterface.CallFunctionVoid(profileFetch, "push", profile);
     603    }
     604
     605    return CScriptValRooted(cx, profileFetch);
     606}
     607
    559608/*****************************************************
    560609 * Message interfaces                                *
    561610 *****************************************************/
     
    648697    {
    649698        const GameListQuery* gq = iq.findExtension<GameListQuery>( ExtGameListQuery );
    650699        const BoardListQuery* bq = iq.findExtension<BoardListQuery>( ExtBoardListQuery );
     700        const ProfileQuery* pq = iq.findExtension<ProfileQuery>( ExtProfileQuery );
    651701        if(gq)
    652702        {
    653703            for(std::vector<const glooxwrapper::Tag*>::const_iterator it = m_GameList.begin(); it != m_GameList.end(); ++it )
     
    684734                CreateSimpleMessage("system", "ratinglist updated", "internal");
    685735            }
    686736        }
     737        if (pq)
     738        {
     739            for (std::vector<const glooxwrapper::Tag*>::const_iterator it = m_Profile.begin(); it != m_Profile.end(); ++it)
     740                glooxwrapper::Tag::free(*it);
     741            m_Profile.clear();
     742
     743            for (std::vector<const glooxwrapper::Tag*>::const_iterator it = pq->m_StanzaProfile.begin(); it != pq->m_StanzaProfile.end(); ++it)
     744                m_Profile.push_back((*it)->clone());
     745
     746            CreateSimpleMessage("system", "profile updated", "internal");
     747        }
    687748    }
    688     else if(iq.subtype() == gloox::IQ::Error)
     749    else if (iq.subtype() == gloox::IQ::Error)
    689750    {
    690751        gloox::StanzaError err = iq.error_error();
    691752        std::string msg = StanzaErrorToString(err);
  • source/lobby/XmppClient.h

     
    6161    void SendIqGetGameList();
    6262    void SendIqGetBoardList();
    6363    void SendIqGetRatingList();
     64    void SendIqGetProfile(const std::string& player);
    6465    void SendIqGameReport(ScriptInterface& scriptInterface, CScriptVal data);
    6566    void SendIqRegisterGame(ScriptInterface& scriptInterface, CScriptVal data);
    6667    void SendIqUnregisterGame();
     
    7778    CScriptValRooted GUIGetPlayerList(ScriptInterface& scriptInterface);
    7879    CScriptValRooted GUIGetGameList(ScriptInterface& scriptInterface);
    7980    CScriptValRooted GUIGetBoardList(ScriptInterface& scriptInterface);
     81    CScriptValRooted GUIGetProfile(ScriptInterface& scriptInterface);
    8082    //Script
    8183    ScriptInterface& GetScriptInterface();
    8284
     
    143145    std::vector<const glooxwrapper::Tag*> m_GameList;
    144146    /// List of rankings
    145147    std::vector<const glooxwrapper::Tag*> m_BoardList;
     148    /// Profile data
     149    std::vector<const glooxwrapper::Tag*> m_Profile;
    146150    /// Queue of messages for the GUI
    147151    std::deque<GUIMessage> m_GuiMessageQueue;
    148152    /// Current room subject/topic.
  • source/lobby/scripting/JSInterface_Lobby.cpp

     
    101101    g_XmppClient->SendIqGetRatingList();
    102102}
    103103
     104void JSI_Lobby::SendGetProfile(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring player)
     105{
     106    if (!g_XmppClient)
     107        return;
     108    g_XmppClient->SendIqGetProfile(utf8_from_wstring(player));
     109}
     110
    104111void JSI_Lobby::SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal data)
    105112{
    106113    if (!g_XmppClient)
     
    161168    return boardList.get();
    162169}
    163170
     171CScriptVal JSI_Lobby::GetProfile(ScriptInterface::CxPrivate* pCxPrivate)
     172{
     173    if (!g_XmppClient)
     174        return CScriptVal();
     175
     176    CScriptValRooted profileFetch = g_XmppClient->GUIGetProfile(*(pCxPrivate->pScriptInterface));
     177
     178    return profileFetch.get();
     179}
     180
    164181CScriptVal JSI_Lobby::LobbyGuiPollMessage(ScriptInterface::CxPrivate* pCxPrivate)
    165182{
    166183    if (!g_XmppClient)
  • source/lobby/scripting/JSInterface_Lobby.h

     
    3737    void SendGetGameList(ScriptInterface::CxPrivate* pCxPrivate);
    3838    void SendGetBoardList(ScriptInterface::CxPrivate* pCxPrivate);
    3939    void SendGetRatingList(ScriptInterface::CxPrivate* pCxPrivate);
     40    void SendGetProfile(ScriptInterface::CxPrivate* pCxPrivate, std::wstring player);
    4041    void SendGameReport(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal data);
    4142    void SendRegisterGame(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal data);
    4243    void SendUnregisterGame(ScriptInterface::CxPrivate* pCxPrivate);
     
    4445    CScriptVal GetPlayerList(ScriptInterface::CxPrivate* pCxPrivate);
    4546    CScriptVal GetGameList(ScriptInterface::CxPrivate* pCxPrivate);
    4647    CScriptVal GetBoardList(ScriptInterface::CxPrivate* pCxPrivate);
     48    CScriptVal GetProfile(ScriptInterface::CxPrivate* pCxPrivate);
    4749    CScriptVal LobbyGuiPollMessage(ScriptInterface::CxPrivate* pCxPrivate);
    4850    void LobbySendMessage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring message);
    4951    void LobbySetPlayerPresence(ScriptInterface::CxPrivate* pCxPrivate, std::wstring presence);
  • source/tools/XpartaMuPP/LobbyRanking.py

     
    3333    id = Column(Integer, primary_key=True)
    3434    jid = Column(String(255))
    3535    rating = Column(Integer)
     36    highest_rating = Column(Integer)
    3637    games = relationship('Game', secondary='players_info')
    3738    # These two relations really only exist to satisfy the linkage
    3839    # between PlayerInfo and Player and Game and player.
  • source/tools/XpartaMuPP/XpartaMuPP.py

     
    2626from sleekxmpp.xmlstream.handler import Callback
    2727from sleekxmpp.xmlstream.matcher import StanzaPath
    2828
     29from sqlalchemy import func
     30
    2931from LobbyRanking import session as db, Game, Player, PlayerInfo
    3032from ELO import get_rating_adjustment
    3133# Rating that new players should be inserted into the
     
    3739  def __init__(self, room):
    3840    self.room = room
    3941    self.lastRated = ""
     42
     43  def getProfile(self, JID):
     44    """
     45      Retrieves the profile for the specified JID
     46    """
     47    stats = {}
     48    player = db.query(Player).filter(Player.jid.ilike(str(JID)))
     49    if not player.first():
     50      return
     51    if player.first().rating != -1:
     52      stats['rating'] = str(player.first().rating)
     53
     54    if player.first().highest_rating != -1:
     55      stats['highestRating'] = str(player.first().highest_rating)
     56
     57    playerID = player.first().id
     58    players = db.query(Player).order_by(Player.rating.desc()).all()
     59
     60    for rank, user in enumerate(players):
     61      if (user.jid.lower() == JID.lower()):
     62        stats['rank'] = str(rank+1)
     63        break
     64
     65    stats['totalGamesPlayed'] = str(db.query(PlayerInfo).filter_by(player_id=playerID).count())
     66    stats['wins'] = str(db.query(Game).filter_by(winner_id=playerID).count())
     67    stats['losses'] = str(db.query(PlayerInfo).filter_by(player_id=playerID).count() - db.query(Game).filter_by(winner_id=playerID).count())
     68    return stats
     69
    4070  def getOrCreatePlayer(self, JID):
    4171    """
    4272      Stores a player(JID) in the database if they don't yet exist.
     
    180210      name2, player2.rating, player2.rating + rating_adjustment2)
    181211    player1.rating += rating_adjustment1
    182212    player2.rating += rating_adjustment2
     213    if not player1.highest_rating:
     214      player1.highest_rating = -1
     215    if not player2.highest_rating:
     216      player2.highest_rating = -1
     217    if player1.rating > player1.highest_rating:
     218      player1.highest_rating = player1.rating
     219    if player2.rating > player2.highest_rating:
     220      player2.highest_rating = player2.rating
    183221    db.commit()
    184222    return self
    185223
     
    426464      data[key] = item
    427465    return data
    428466
     467## Class for custom profile ##
     468class ProfileXmppPlugin(ElementBase):
     469  name = 'query'
     470  namespace = 'jabber:iq:profile'
     471  interfaces = set(('profile', 'command'))
     472  sub_interfaces = interfaces
     473  plugin_attrib = 'profile'
     474  def addCommand(self, command):
     475    commandXml = ET.fromstring("<command>%s</command>" % command)
     476    self.xml.append(commandXml)
     477  def addItem(self, player, rating, highestRating, rank, totalGamesPlayed, wins, losses):
     478    itemXml = ET.Element("profile", {"player": player, "rating": rating, "highestRating": highestRating,
     479                                      "rank" : rank, "totalGamesPlayed" : totalGamesPlayed, "wins" : wins,
     480                                      "losses" : losses})
     481    self.xml.append(itemXml)
     482
    429483## Main class which handles IQ data and sends new data ##
    430484class XpartaMuPP(sleekxmpp.ClientXMPP):
    431485  """
     
    454508    register_stanza_plugin(Iq, GameListXmppPlugin)
    455509    register_stanza_plugin(Iq, BoardListXmppPlugin)
    456510    register_stanza_plugin(Iq, GameReportXmppPlugin)
     511    register_stanza_plugin(Iq, ProfileXmppPlugin)
    457512
    458513    self.register_handler(Callback('Iq Gamelist',
    459514                                       StanzaPath('iq/gamelist'),
     
    467522                                       StanzaPath('iq/gamereport'),
    468523                                       self.iqhandler,
    469524                                       instream=True))
     525                                       
     526    self.register_handler(Callback('Iq Profile',
     527                                       StanzaPath('iq/profile'),
     528                                       self.iqhandler,
     529                                       instream=True))
    470530
    471531    self.add_event_handler("session_start", self.start)
    472532    self.add_event_handler("muc::%s::got_online" % self.room, self.muc_online)
     
    566626            logging.error("Failed to process ratinglist request from %s" % iq['from'].bare)
    567627        else:
    568628          logging.error("Failed to process boardlist request from %s" % iq['from'].bare)
     629      elif 'profile' in iq.plugins:
     630        command = iq['profile']['command']
     631        try:
     632          self.sendProfile(iq['from'], command)
     633        except:
     634          try:
     635            self.sendProfileNotFound(iq['from'], command)
     636          except:
     637            logging.debug("No record found for %s" % command)
    569638      else:
    570639        logging.error("Unknown 'get' type stanza request from %s" % iq['from'].bare)
    571640    elif iq['type'] == 'result':
     
    748817      except:
    749818        logging.error("Failed to send rating list")
    750819
     820  def sendProfile(self, to, player):
     821    """
     822      Send the profile to a specified player.
     823    """
     824    if to == "":
     825      logging.error("Failed to send profile")
     826      return
     827
     828    online = False;
     829    ## Pull stats and add it to the stanza
     830    for JID in self.nicks.keys():
     831      if self.nicks[JID] == player:
     832        stats = self.leaderboard.getProfile(JID)
     833        online = True
     834        break
     835
     836    if online == False:
     837      stats = self.leaderboard.getProfile(player + "@" + str(to).split('@')[1])
     838    stz = ProfileXmppPlugin()
     839    iq = self.Iq()
     840    iq['type'] = 'result'
     841
     842    stz.addItem(player, stats['rating'], stats['highestRating'], stats['rank'], stats['totalGamesPlayed'], stats['wins'], stats['losses'])
     843    stz.addCommand(player)
     844    iq.setPayload(stz)
     845    ## Check recipient exists
     846    if str(to) not in self.nicks:
     847      logging.error("No player with the XmPP ID '%s' known to send profile to" % str(to))
     848      return
     849
     850    ## Set additional IQ attributes
     851    iq['to'] = to
     852
     853    ## Try sending the stanza
     854    try:
     855      iq.send(block=False, now=True)
     856    except:
     857      traceback.print_exc()
     858      logging.error("Failed to send profile")
     859
     860  def sendProfileNotFound(self, to, player):
     861    """
     862      Send a profile not-found error to a specified player.
     863    """
     864    stz = ProfileXmppPlugin()
     865    iq = self.Iq()
     866    iq['type'] = 'result'
     867
     868    filler = str(0)
     869    stz.addItem(player, str(-2), filler, filler, filler, filler, filler)
     870    stz.addCommand(player)
     871    iq.setPayload(stz)
     872    ## Check recipient exists
     873    if str(to) not in self.nicks:
     874      logging.error("No player with the XmPP ID '%s' known to send profile to" % str(to))
     875      return
     876
     877    ## Set additional IQ attributes
     878    iq['to'] = to
     879
     880    ## Try sending the stanza
     881    try:
     882      iq.send(block=False, now=True)
     883    except:
     884      traceback.print_exc()
     885      logging.error("Failed to send profile")
     886
    751887## Main Program ##
    752888if __name__ == '__main__':
    753889  # Setup the command line arguments.