diff options
Diffstat (limited to 'src/gui/windows/whoisonline.cpp')
-rw-r--r-- | src/gui/windows/whoisonline.cpp | 866 |
1 files changed, 866 insertions, 0 deletions
diff --git a/src/gui/windows/whoisonline.cpp b/src/gui/windows/whoisonline.cpp new file mode 100644 index 000000000..dafbb0419 --- /dev/null +++ b/src/gui/windows/whoisonline.cpp @@ -0,0 +1,866 @@ +/* + * The ManaPlus Client + * Copyright (C) 2009 The Mana World Development Team + * Copyright (C) 2009-2010 Andrei Karas + * Copyright (C) 2011-2013 The ManaPlus developers + * + * This file is part of The ManaPlus Client. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "gui/windows/whoisonline.h" + +#include "guild.h" +#include "party.h" + +#include "gui/viewport.h" + +#include "gui/windows/chatwindow.h" +#include "gui/windows/socialwindow.h" + +#include "gui/widgets/button.h" +#include "gui/widgets/browserbox.h" +#include "gui/widgets/scrollarea.h" + +#include "actorspritemanager.h" +#include "client.h" +#include "configuration.h" +#include "main.h" + +#include "being/localplayer.h" +#include "being/playerrelations.h" + +#include "net/download.h" +#include "net/net.h" +#include "net/playerhandler.h" + +#include "utils/gettext.h" +#include "utils/sdlhelper.h" + +#include <SDL_thread.h> +#include <vector> +#include <algorithm> + +// Curl should be included after Guichan to avoid Windows redefinitions +#include <curl/curl.h> + +#include "debug.h" + +#ifdef free +#undef free +#endif + +#ifdef malloc +#undef malloc +#endif + +class NameFunctuator final +{ + public: + bool operator()(const OnlinePlayer *left, + const OnlinePlayer *right) const + { + return (compareStrI(left->getNick(), right->getNick()) < 0); + } +} nameCompare; + +WhoIsOnline::WhoIsOnline() : + // TRANSLATORS: who is online window name + Window(_("Who Is Online - Updating"), false, nullptr, "whoisonline.xml"), + mThread(nullptr), + mDownloadStatus(UPDATE_LIST), + mDownloadComplete(true), + mDownloadedBytes(0), + mMemoryBuffer(nullptr), + mCurlError(new char[CURL_ERROR_SIZE]), + mBrowserBox(new BrowserBox(this)), + mScrollArea(new ScrollArea(mBrowserBox, false)), + mUpdateTimer(0), + mOnlinePlayers(), + mOnlineNicks(), + // TRANSLATORS: who is online. button. + mUpdateButton(new Button(this, _("Update"), "update", this)), + mAllowUpdate(true), + mShowLevel(false), + mUpdateOnlineList(config.getBoolValue("updateOnlineList")), + + mGroupFriends(true) +{ + mCurlError[0] = 0; + setWindowName("WhoIsOnline"); + + const int h = 350; + const int w = 200; + setDefaultSize(w, h, ImageRect::CENTER); + setVisible(false); + setCloseButton(true); + setResizable(true); + setStickyButtonLock(true); + setSaveVisible(true); + + mUpdateButton->setEnabled(false); + mUpdateButton->setDimension(gcn::Rectangle(5, 5, w - 10, 20 + 5)); + + mBrowserBox->setOpaque(false); + mBrowserBox->setHighlightMode(BrowserBox::BACKGROUND); + mScrollArea->setDimension(gcn::Rectangle(5, 20 + 10, w - 10, h - 10 - 30)); + mScrollArea->setSize(w - 10, h - 10 - 30); + mBrowserBox->setLinkHandler(this); + + add(mUpdateButton); + add(mScrollArea); + + setLocationRelativeTo(getParent()); + + loadWindowState(); + enableVisibleSound(true); + + download(); + + widgetResized(gcn::Event(nullptr)); + config.addListener("updateOnlineList", this); + config.addListener("groupFriends", this); + mGroupFriends = config.getBoolValue("groupFriends"); +} + +WhoIsOnline::~WhoIsOnline() +{ + config.removeListeners(this); + + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, nullptr); + + free(mMemoryBuffer); + mMemoryBuffer = nullptr; + + // Remove possibly leftover temporary download + delete []mCurlError; + + FOR_EACH (std::set<OnlinePlayer*>::iterator, itd, mOnlinePlayers) + delete *itd; + mOnlinePlayers.clear(); + mOnlineNicks.clear(); +} + +void WhoIsOnline::handleLink(const std::string& link, gcn::MouseEvent *event) +{ + if (!event || event->getButton() == gcn::MouseEvent::LEFT) + { + if (chatWindow) + { + const std::string text = decodeLinkText(link); + if (config.getBoolValue("whispertab")) + { + chatWindow->localChatInput("/q " + text); + } + else + { + chatWindow->addInputText(std::string("/w \"").append( + text).append("\" ")); + } + } + } + else if (event->getButton() == gcn::MouseEvent::RIGHT) + { + if (player_node && link == player_node->getName()) + return; + + if (viewport) + { + if (actorSpriteManager) + { + const std::string text = decodeLinkText(link); + Being *const being = actorSpriteManager->findBeingByName( + text, Being::PLAYER); + + if (being && viewport) + { + viewport->showPopup(being); + return; + } + } + viewport->showPlayerPopup(link); + } + } +} + +void WhoIsOnline::updateWindow(std::vector<OnlinePlayer*> &friends, + std::vector<OnlinePlayer*> &neutral, + std::vector<OnlinePlayer*> &disregard, + std::vector<OnlinePlayer*> enemy, + size_t numOnline) +{ + // Set window caption + // TRANSLATORS: who is online window name + setCaption(_("Who Is Online - ") + toString(numOnline)); + + // List the online people + std::sort(friends.begin(), friends.end(), nameCompare); + std::sort(neutral.begin(), neutral.end(), nameCompare); + std::sort(disregard.begin(), disregard.end(), nameCompare); + bool addedFromSection(false); + FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, friends) + { + mBrowserBox->addRow((*it)->getText()); + addedFromSection = true; + } + if (addedFromSection == true) + { + mBrowserBox->addRow("---"); + addedFromSection = false; + } + FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, enemy) + { + mBrowserBox->addRow((*it)->getText()); + addedFromSection = true; + } + if (addedFromSection == true) + { + mBrowserBox->addRow("---"); + addedFromSection = false; + } + FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, neutral) + { + mBrowserBox->addRow((*it)->getText()); + addedFromSection = true; + } + if (addedFromSection == true && !disregard.empty()) + mBrowserBox->addRow("---"); + + FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, disregard) + mBrowserBox->addRow((*it)->getText()); + + if (mScrollArea->getVerticalMaxScroll() < + mScrollArea->getVerticalScrollAmount()) + { + mScrollArea->setVerticalScrollAmount( + mScrollArea->getVerticalMaxScroll()); + } +} + +void WhoIsOnline::loadList(std::vector<OnlinePlayer*> &list) +{ + mBrowserBox->clearRows(); + const size_t numOnline = list.size(); + std::vector<OnlinePlayer*> friends; + std::vector<OnlinePlayer*> neutral; + std::vector<OnlinePlayer*> disregard; + std::vector<OnlinePlayer*> enemy; + + FOR_EACH (std::set<OnlinePlayer*>::iterator, itd, mOnlinePlayers) + delete *itd; + mOnlinePlayers.clear(); + mOnlineNicks.clear(); + + mShowLevel = config.getBoolValue("showlevel"); + + FOR_EACH (std::vector<OnlinePlayer*>::const_iterator, it, list) + { + OnlinePlayer *player = *it; + const std::string nick = player->getNick(); + mOnlinePlayers.insert(player); + mOnlineNicks.insert(nick); + + if (!mShowLevel) + player->setLevel(0); + + switch (player_relations.getRelation(nick)) + { + case PlayerRelation::NEUTRAL: + default: + setNeutralColor(player); + neutral.push_back(player); + break; + + case PlayerRelation::FRIEND: + player->setText("2"); + if (mGroupFriends) + friends.push_back(player); + else + neutral.push_back(player); + break; + + case PlayerRelation::DISREGARDED: + case PlayerRelation::BLACKLISTED: + player->setText("8"); + disregard.push_back(player); + break; + + case PlayerRelation::ENEMY2: + player->setText("1"); + enemy.push_back(player); + break; + + case PlayerRelation::IGNORED: + case PlayerRelation::ERASED: + // Ignore the ignored. + break; + } + } + + updateWindow(friends, neutral, disregard, enemy, numOnline); + if (!mOnlineNicks.empty()) + { + if (chatWindow) + chatWindow->updateOnline(mOnlineNicks); + if (socialWindow) + socialWindow->updateActiveList(); + } + updateSize(); +} + +void WhoIsOnline::loadWebList() +{ + if (!mMemoryBuffer) + return; + + // Reallocate and include terminating 0 character + mMemoryBuffer = static_cast<char*>( + realloc(mMemoryBuffer, mDownloadedBytes + 1)); + mMemoryBuffer[mDownloadedBytes] = '\0'; + + mBrowserBox->clearRows(); + bool listStarted(false); + std::string lineStr; + int numOnline(0); + std::vector<OnlinePlayer*> friends; + std::vector<OnlinePlayer*> neutral; + std::vector<OnlinePlayer*> disregard; + std::vector<OnlinePlayer*> enemy; + + // Tokenize and add each line separately + char *line = strtok(mMemoryBuffer, "\n"); + const std::string gmText("(GM)"); + + FOR_EACH (std::set<OnlinePlayer*>::iterator, itd, mOnlinePlayers) + delete *itd; + + mOnlinePlayers.clear(); + mOnlineNicks.clear(); + + mShowLevel = config.getBoolValue("showlevel"); + + while (line) + { + std::string nick; + lineStr = line; + trim(lineStr); + if (listStarted == true) + { + if (lineStr.find(" users are online.") == std::string::npos) + { + if (lineStr.length() > 24) + { + nick = lineStr.substr(0, 24); + lineStr = lineStr.substr(25); + } + else + { + nick = lineStr; + lineStr.clear(); + } + trim(nick); + + bool isGM(false); + size_t pos = lineStr.find(gmText, 0); + if (pos != std::string::npos) + { + lineStr = lineStr.substr(pos + gmText.length()); + isGM = true; + } + + trim(lineStr); + pos = lineStr.find("/", 0); + + if (pos != std::string::npos) + lineStr = lineStr.substr(0, pos); + + int level = 0; + if (!lineStr.empty()) + level = atoi(lineStr.c_str()); + + if (actorSpriteManager) + { + Being *const being = actorSpriteManager->findBeingByName( + nick, Being::PLAYER); + if (being) + { + if (level > 0) + { + being->setLevel(level); + being->updateName(); + } + else + { + if (being->getLevel() > 1) + level = being->getLevel(); + } + } + } + + if (!mShowLevel) + level = 0; + + OnlinePlayer *const player = new OnlinePlayer(nick, + static_cast<signed char>(255), level, + GENDER_UNSPECIFIED, -1); + mOnlinePlayers.insert(player); + mOnlineNicks.insert(nick); + + if (isGM) + player->setIsGM(true); + + numOnline++; + switch (player_relations.getRelation(nick)) + { + case PlayerRelation::NEUTRAL: + default: + setNeutralColor(player); + neutral.push_back(player); + break; + + case PlayerRelation::FRIEND: + player->setText("2"); + if (mGroupFriends) + friends.push_back(player); + else + neutral.push_back(player); + break; + + case PlayerRelation::DISREGARDED: + case PlayerRelation::BLACKLISTED: + player->setText("8"); + disregard.push_back(player); + break; + + case PlayerRelation::ENEMY2: + player->setText("1"); + enemy.push_back(player); + break; + + case PlayerRelation::IGNORED: + case PlayerRelation::ERASED: + // Ignore the ignored. + break; + } + } + } + else if (lineStr.find("------------------------------") + != std::string::npos) + { + listStarted = true; + } + line = strtok(nullptr, "\n"); + } + + updateWindow(friends, neutral, disregard, enemy, numOnline); + + // Free the memory buffer now that we don't need it anymore + free(mMemoryBuffer); + mMemoryBuffer = nullptr; +} + +size_t WhoIsOnline::memoryWrite(void *ptr, size_t size, + size_t nmemb, FILE *stream) +{ + if (!stream) + return 0; + + WhoIsOnline *const wio = reinterpret_cast<WhoIsOnline *>(stream); + const size_t totalMem = size * nmemb; + wio->mMemoryBuffer = static_cast<char*>(realloc(wio->mMemoryBuffer, + wio->mDownloadedBytes + totalMem)); + if (wio->mMemoryBuffer) + { + memcpy(&(wio->mMemoryBuffer[wio->mDownloadedBytes]), ptr, totalMem); + wio->mDownloadedBytes += static_cast<int>(totalMem); + } + + return totalMem; +} + +int WhoIsOnline::downloadThread(void *ptr) +{ + int attempts = 0; + WhoIsOnline *const wio = reinterpret_cast<WhoIsOnline *>(ptr); + CURLcode res; + const std::string url(client->getOnlineUrl() + "/online.txt"); + + while (attempts < 1 && !wio->mDownloadComplete) + { + CURL *curl = curl_easy_init(); + if (curl) + { + if (!wio->mAllowUpdate) + { + curl_easy_cleanup(curl); + curl = nullptr; + break; + } + wio->mDownloadedBytes = 0; + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, + WhoIsOnline::memoryWrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, ptr); + + curl_easy_setopt(curl, CURLOPT_USERAGENT, + strprintf(PACKAGE_EXTENDED_VERSION, + branding.getStringValue("appName").c_str()).c_str()); + + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, wio->mCurlError); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, ptr); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 7); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30); + Net::Download::addProxy(curl); + Net::Download::secureCurl(curl); + + // Make sure the resources2.txt and news.txt aren't cached, + // in order to always get the latest version. + struct curl_slist *pHeaders = nullptr; + pHeaders = curl_slist_append( + pHeaders, "pragma: no-cache"); + pHeaders = curl_slist_append(pHeaders, + "Cache-Control: no-cache"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders); + + if ((res = curl_easy_perform(curl)) != 0) + { + wio->mDownloadStatus = UPDATE_ERROR; + switch (res) + { + case CURLE_COULDNT_CONNECT: + default: + std::cerr << "curl error " + << static_cast<unsigned>(res) << ": " + << wio->mCurlError << " host: " + << url.c_str() << std::endl; + break; + } + attempts++; + curl_easy_cleanup(curl); + curl_slist_free_all(pHeaders); + curl = nullptr; + continue; + } + + curl_easy_cleanup(curl); + curl_slist_free_all(pHeaders); + + // It's stored in memory, we're done + wio->mDownloadComplete = true; + } + if (!wio->mAllowUpdate) + break; + attempts++; + } + + if (!wio->mDownloadComplete) + wio->mDownloadStatus = UPDATE_ERROR; + return 0; +} + +void WhoIsOnline::download() +{ + if (serverVersion < 3) + { + mDownloadComplete = true; + if (mThread && SDL_GetThreadID(mThread)) + SDL_WaitThread(mThread, nullptr); + + mDownloadComplete = false; + mThread = SDL::createThread(WhoIsOnline::downloadThread, + "whoisonline", this); + if (mThread == nullptr) + mDownloadStatus = UPDATE_ERROR; + } + else + { + if (client->limitPackets(PACKET_ONLINELIST)) + Net::getPlayerHandler()->requestOnlineList(); + } +} + +void WhoIsOnline::logic() +{ + BLOCK_START("WhoIsOnline::logic") + mScrollArea->logic(); + BLOCK_END("WhoIsOnline::logic") +} + +void WhoIsOnline::slowLogic() +{ + if (!mAllowUpdate) + return; + + BLOCK_START("WhoIsOnline::slowLogic") + if (mUpdateTimer == 0) + mUpdateTimer = cur_time; + + const double timeDiff = difftime(cur_time, mUpdateTimer); + const int timeLimit = isWindowVisible() ? 20 : 120; + + if (mUpdateOnlineList && timeDiff >= timeLimit + && mDownloadStatus != UPDATE_LIST) + { + if (mDownloadComplete == true) + { + // TRANSLATORS: who is online window name + setCaption(_("Who Is Online - Updating")); + mUpdateTimer = 0; + mDownloadStatus = UPDATE_LIST; + download(); + } + } + + switch (mDownloadStatus) + { + case UPDATE_ERROR: + mBrowserBox->clearRows(); + mBrowserBox->addRow("##1Failed to fetch the online list!"); + mBrowserBox->addRow(mCurlError); + mDownloadStatus = UPDATE_COMPLETE; + // TRANSLATORS: who is online window name + setCaption(_("Who Is Online - error")); + mUpdateButton->setEnabled(true); + mUpdateTimer = cur_time + 240; + mDownloadComplete = true; + updateSize(); + break; + case UPDATE_LIST: + if (mDownloadComplete == true) + { + loadWebList(); + mDownloadStatus = UPDATE_COMPLETE; + mUpdateButton->setEnabled(true); + mUpdateTimer = 0; + updateSize(); + if (!mOnlineNicks.empty()) + { + if (chatWindow) + chatWindow->updateOnline(mOnlineNicks); + if (socialWindow) + socialWindow->updateActiveList(); + } + } + break; + case UPDATE_COMPLETE: + default: + break; + } + BLOCK_END("WhoIsOnline::slowLogic") +} + +void WhoIsOnline::action(const gcn::ActionEvent &event) +{ + if (event.getId() == "update") + { + if (serverVersion < 3) + { + if (mDownloadStatus == UPDATE_COMPLETE) + { + mUpdateTimer = cur_time - 20; + if (mUpdateButton) + mUpdateButton->setEnabled(false); + // TRANSLATORS: who is online window name + setCaption(_("Who Is Online - Update")); + if (mThread && SDL_GetThreadID(mThread)) + { + SDL_WaitThread(mThread, nullptr); + mThread = nullptr; + } + mDownloadComplete = true; + } + } + else + { + if (client->limitPackets(PACKET_ONLINELIST)) + { + mUpdateTimer = cur_time; + Net::getPlayerHandler()->requestOnlineList(); + } + } + } +} + +void WhoIsOnline::widgetResized(const gcn::Event &event) +{ + Window::widgetResized(event); + updateSize(); +} + +void WhoIsOnline::updateSize() +{ + const gcn::Rectangle area = getChildrenArea(); + if (mUpdateButton) + mUpdateButton->setWidth(area.width - 10); + + if (mScrollArea) + mScrollArea->setSize(area.width - 10, area.height - 10 - 30); + if (mBrowserBox) + mBrowserBox->setWidth(area.width - 10); +} + +const std::string WhoIsOnline::prepareNick(const std::string &nick, + const int level, + const std::string &color) const +{ + const std::string text = encodeLinkText(nick); + if (mShowLevel && level > 1) + { + return strprintf("@@%s|##%s%s (%d)@@", text.c_str(), + color.c_str(), text.c_str(), level); + } + else + { + return strprintf("@@%s|##%s%s@@", text.c_str(), + color.c_str(), text.c_str()); + } +} + +void WhoIsOnline::optionChanged(const std::string &name) +{ + if (name == "updateOnlineList") + mUpdateOnlineList = config.getBoolValue("updateOnlineList"); + else if (name == "groupFriends") + mGroupFriends = config.getBoolValue("groupFriends"); +} + +void WhoIsOnline::setNeutralColor(OnlinePlayer *const player) +{ + if (!player) + return; + + if (actorSpriteManager && player_node) + { + const std::string &nick = player->getNick(); + if (nick == player_node->getName()) + { + player->setText("s"); + return; + } + if (player_node->isInParty()) + { + const Party *const party = player_node->getParty(); + if (party) + { + if (party->getMember(nick)) + { + player->setText("P"); + return; + } + } + } + + const Being *const being = actorSpriteManager->findBeingByName(nick); + if (being) + { + const Guild *const guild2 = player_node->getGuild(); + if (guild2) + { + const Guild *const guild1 = being->getGuild(); + if (guild1) + { + if (guild1->getId() == guild2->getId() + || guild2->getMember(nick)) + { + player->setText("U"); + return; + } + } + else if (guild2->isMember(nick)) + { + player->setText("U"); + return; + } + } + } + const Guild *const guild3 = Guild::getGuild(1); + if (guild3 && guild3->isMember(nick)) + { + player->setText("U"); + return; + } + } + player->setText("0"); +} + +void WhoIsOnline::getPlayerNames(StringVect &names) +{ + names.clear(); + FOR_EACH (std::set<std::string>::const_iterator, it, mOnlineNicks) + names.push_back(*it); +} + +void OnlinePlayer::setText(std::string color) +{ + mText.clear(); + + if (mStatus != 255 && actorSpriteManager) + { + Being *const being = actorSpriteManager->findBeingByName( + mNick, Being::PLAYER); + if (being) + { + being->setState(mStatus); + // for now highlight versions > 3 + if (mVersion > 3) + being->setAdvanced(true); + } + } + + if ((mStatus != 255 && mStatus & Being::FLAG_GM) || mIsGM) + mText.append("(GM) "); + + if (mLevel > 0) + mText.append(strprintf("%d", mLevel)); + + if (mGender == GENDER_FEMALE) + mText.append("\u2640"); + else if (mGender == GENDER_MALE) + mText.append("\u2642"); + + if (mStatus > 0 && mStatus != 255) + { + if (mStatus & Being::FLAG_SHOP) + mText.append("$"); + if (mStatus & Being::FLAG_AWAY) + { + // TRANSLATORS: this away status writed in player nick + mText.append(_("A")); + } + if (mStatus & Being::FLAG_INACTIVE) + { + // TRANSLATORS: this inactive status writed in player nick + mText.append(_("I")); + } + + if (mStatus & Being::FLAG_GM && color == "0") + color = "2"; + } + else if (mIsGM && color == "0") + { + color = "2"; + } + + if (mVersion > 0) + mText.append(strprintf(" - %d", mVersion)); + + const std::string text = encodeLinkText(mNick); + mText = strprintf("@@%s|##%s%s %s@@", text.c_str(), color.c_str(), + text.c_str(), mText.c_str()); +} |