Add a list of servers to the "Multiplayer" tab
authorJeija <norrepli@gmail.com>
Tue, 25 Dec 2012 11:20:51 +0000 (12:20 +0100)
committerPerttu Ahola <celeron55@gmail.com>
Mon, 21 Jan 2013 14:42:56 +0000 (16:42 +0200)
If USE_CURL is set, it also downloads a list from a remote server.
The url of this list is configurable in minetest.conf using the setting "serverlist_url"
The local list of favorite servers is saved in client/serverlist/filename
filename is also configureable using the setting "serverlist_file"

client/serverlist/.gitignore [new file with mode: 0644]
minetest.conf.example
src/CMakeLists.txt
src/defaultsettings.cpp
src/guiMainMenu.cpp
src/guiMainMenu.h
src/main.cpp
src/serverlist.cpp [new file with mode: 0644]
src/serverlist.h [new file with mode: 0644]

diff --git a/client/serverlist/.gitignore b/client/serverlist/.gitignore
new file mode 100644 (file)
index 0000000..d6b7ef3
--- /dev/null
@@ -0,0 +1,2 @@
+*
+!.gitignore
index 3f3256a0466097ed070abf75b35866c4e99c1da6..5978d69878a0caa6b9e8201fb570903616c306f7 100644 (file)
 # and only for clients compiled with cURL
 #media_fetch_threads = 8
 
+# Url to the server list displayed in the Multiplayer Tab
+#serverlist_url = servers.minetest.ru/server.list
+# File in client/serverlist/ that contains your favorite servers displayed in the Multiplayer Tab
+#serverlist_file = favoriteservers.txt
+
 #
 # Server stuff
 #
index 24f682f3f5dc5c97289f41eaa4d559cc751d5749..8e7d29ff0703e30057ea3132ccfaa61b2032b8c9 100644 (file)
@@ -280,6 +280,7 @@ set(minetest_SRCS
        filecache.cpp
        tile.cpp
        shader.cpp
+       serverlist.cpp
        game.cpp
        main.cpp
 )
index ca5f33609ce4b0803a886dcaca2ce7a16247c0f4..163e99c6975d9d79b568eca057add8051a44d27d 100644 (file)
@@ -128,6 +128,9 @@ void set_default_settings(Settings *settings)
 
        settings->setDefault("media_fetch_threads", "8");
 
+       settings->setDefault("serverlist_url", "servers.minetest.ru/server.list");
+       settings->setDefault("serverlist_file", "favoriteservers.txt");
+
        // Server stuff
        // "map-dir" doesn't exist by default.
        settings->setDefault("default_game", "minetest");
index bac9052b946ad484f948fd111a26726444d482bf..ca0c1317c35d46102d2bcf4030737242d78835b8 100644 (file)
@@ -114,6 +114,9 @@ enum
        GUI_ID_CONFIGURE_WORLD_BUTTON,
        GUI_ID_WORLD_LISTBOX,
        GUI_ID_TAB_CONTROL,
+       GUI_ID_SERVERLIST,
+       GUI_ID_SERVERLIST_TOGGLE,
+       GUI_ID_SERVERLIST_DELETE,
 };
 
 enum
@@ -361,14 +364,14 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
                // Nickname + password
                {
                        core::rect<s32> rect(0, 0, 110, 20);
-                       rect += m_topleft_client + v2s32(35+30, 50+6);
+                       rect += m_topleft_client + v2s32(m_size_client.X-60-100, 10+6);
                        Environment->addStaticText(wgettext("Name/Password"), 
                                rect, false, true, this, -1);
                }
                changeCtype("C");
                {
-                       core::rect<s32> rect(0, 0, 230, 30);
-                       rect += m_topleft_client + v2s32(160+30, 50);
+                       core::rect<s32> rect(0, 0, 120, 30);
+                       rect += m_topleft_client + v2s32(m_size_client.X-60-100, 50);
                        gui::IGUIElement *e = 
                        Environment->addEditBox(m_data->name.c_str(), rect, true, this, GUI_ID_NAME_INPUT);
                        if(m_data->name == L"")
@@ -376,7 +379,7 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
                }
                {
                        core::rect<s32> rect(0, 0, 120, 30);
-                       rect += m_topleft_client + v2s32(m_size_client.X-60-100, 50);
+                       rect += m_topleft_client + v2s32(m_size_client.X-60-100, 90);
                        gui::IGUIEditBox *e =
                        Environment->addEditBox(L"", rect, true, this, 264);
                        e->setPasswordBox(true);
@@ -385,17 +388,29 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
 
                }
                changeCtype("");
+               // Server List
+               {
+                       core::rect<s32> rect(0, 0, 390, 160);
+                       rect += m_topleft_client + v2s32(50, 10);
+                       gui::IGUIListBox *e = Environment->addListBox(rect, this,
+                                       GUI_ID_SERVERLIST);
+                       e->setDrawBackground(true);
+                       if (m_data->serverlist_show_available == false)
+                               m_data->servers = ServerList::getLocal();
+                       updateGuiServerList();
+                       e->setSelected(0);
+               }
                // Address + port
                {
                        core::rect<s32> rect(0, 0, 110, 20);
-                       rect += m_topleft_client + v2s32(35+30, 100+6);
+                       rect += m_topleft_client + v2s32(50, m_size_client.Y-50-15+6);
                        Environment->addStaticText(wgettext("Address/Port"),
                                rect, false, true, this, -1);
                }
                changeCtype("C");
                {
-                       core::rect<s32> rect(0, 0, 230, 30);
-                       rect += m_topleft_client + v2s32(160+30, 100);
+                       core::rect<s32> rect(0, 0, 260, 30);
+                       rect += m_topleft_client + v2s32(50, m_size_client.Y-25-15);
                        gui::IGUIElement *e = 
                        Environment->addEditBox(m_data->address.c_str(), rect, true,
                                        this, GUI_ID_ADDRESS_INPUT);
@@ -404,18 +419,43 @@ void GUIMainMenu::regenerateGui(v2u32 screensize)
                }
                {
                        core::rect<s32> rect(0, 0, 120, 30);
-                       rect += m_topleft_client + v2s32(m_size_client.X-60-100, 100);
+                       rect += m_topleft_client + v2s32(50+260+10, m_size_client.Y-25-15);
                        Environment->addEditBox(m_data->port.c_str(), rect, true,
                                        this, GUI_ID_PORT_INPUT);
                }
                changeCtype("");
+               #if USE_CURL
+               // Toggle Serverlist (Favorites/Online)
+               {
+                       core::rect<s32> rect(0, 0, 260, 30);
+                       rect += m_topleft_client + v2s32(50,
+                                       180);
+                       gui::IGUIButton *e = Environment->addButton(rect, this, GUI_ID_SERVERLIST_TOGGLE,
+                               wgettext("Show Public"));
+                       e->setIsPushButton(true);
+                       if (m_data->serverlist_show_available)
+                       {
+                               e->setText(wgettext("Show Favorites"));
+                               e->setPressed();
+                       }
+               }
+               #endif
+               // Delete Local Favorite
+               {
+                       core::rect<s32> rect(0, 0, 120, 30);
+                       rect += m_topleft_client + v2s32(50+260+10, 180);
+                       gui::IGUIButton *e = Environment->addButton(rect, this, GUI_ID_SERVERLIST_DELETE,
+                               wgettext("Delete"));
+                       if (m_data->serverlist_show_available) // Hidden on Show-Online mode
+                               e->setVisible(false);
+               }
                // Start game button
                {
-                       core::rect<s32> rect(0, 0, 180, 30);
-                       rect += m_topleft_client + v2s32(m_size_client.X-180-30,
-                                       m_size_client.Y-30-15);
+                       core::rect<s32> rect(0, 0, 120, 30);
+                       rect += m_topleft_client + v2s32(m_size_client.X-130-30,
+                                       m_size_client.Y-25-15);
                        Environment->addButton(rect, this, GUI_ID_JOIN_GAME_BUTTON,
-                               wgettext("Start Game / Connect"));
+                               wgettext("Connect"));
                }
                changeCtype("C");
        }
@@ -868,6 +908,12 @@ void GUIMainMenu::readInput(MainMenuData *dst)
                if(e != NULL && e->getType() == gui::EGUIET_LIST_BOX)
                        dst->selected_world = ((gui::IGUIListBox*)e)->getSelected();
        }
+       {
+               ServerListSpec server =
+               getServerListSpec(wide_to_narrow(dst->address), wide_to_narrow(dst->port));
+               dst->servername = server.name;
+               dst->serverdescription = server.description;
+       }
 }
 
 void GUIMainMenu::acceptInput()
@@ -912,6 +958,11 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
                                regenerateGui(m_screensize_old);
                        return true;
                }
+               if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED && event.GUIEvent.Caller->getID() == GUI_ID_SERVERLIST)
+               {
+                       serverListOnSelected();
+                       return true;
+               }
                if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED)
                {
                        switch(event.GUIEvent.Caller->getID())
@@ -919,7 +970,8 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
                        case GUI_ID_JOIN_GAME_BUTTON: {
                                MainMenuData cur;
                                readInput(&cur);
-                               if(cur.address == L"" && getTab() == TAB_MULTIPLAYER){
+                               if (getTab() == TAB_MULTIPLAYER && cur.address == L"")
+                               {
                                        (new GUIMessageMenu(env, parent, -1, menumgr,
                                                        wgettext("Address required."))
                                                        )->drop();
@@ -987,6 +1039,45 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
                                menu->drop();
                                return true;
                        }
+                       case GUI_ID_SERVERLIST_DELETE: {
+                               gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST);
+                               u16 selected = ((gui::IGUIListBox*)serverlist)->getSelected();
+                               if (selected == -1) return true;
+                               ServerList::deleteEntry(m_data->servers[selected]);
+                               m_data->servers = ServerList::getLocal();
+                               updateGuiServerList();
+                               if (selected > 0)
+                                       selected -= 1;
+                               serverlist->setSelected(selected);
+                               serverListOnSelected();
+                               return true;
+                       }
+                       #if USE_CURL
+                       case GUI_ID_SERVERLIST_TOGGLE: {
+                               gui::IGUIElement *togglebutton = getElementFromId(GUI_ID_SERVERLIST_TOGGLE);
+                               gui::IGUIElement *deletebutton = getElementFromId(GUI_ID_SERVERLIST_DELETE);
+                               gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST);
+                               if (m_data->serverlist_show_available) // switch to favorite list
+                               {
+                                       m_data->servers = ServerList::getLocal();
+                                       togglebutton->setText(wgettext("Show Public"));
+                                       deletebutton->setVisible(true);
+                                       updateGuiServerList();
+                                       serverlist->setSelected(0);
+                               }
+                               else // switch to online list
+                               {
+                                       m_data->servers = ServerList::getOnline();
+                                       togglebutton->setText(wgettext("Show Favorites"));
+                                       deletebutton->setVisible(false);
+                                       updateGuiServerList();
+                                       serverlist->setSelected(0);
+                               }
+                               serverListOnSelected();
+
+                               m_data->serverlist_show_available = !m_data->serverlist_show_available;
+                       }
+                       #endif
                        }
                }
                if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER)
@@ -1009,6 +1100,14 @@ bool GUIMainMenu::OnEvent(const SEvent& event)
                                        m_data->address = L""; // Force local game
                                quitMenu();
                                return true;
+                       case GUI_ID_SERVERLIST:
+                               gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST);
+                               if (serverlist->getSelected() > -1)
+                               {
+                                       acceptInput();
+                                       quitMenu();
+                                       return true;
+                               }
                        }
                }
        }
@@ -1053,3 +1152,55 @@ void GUIMainMenu::displayMessageMenu(std::wstring msg)
 {
        (new GUIMessageMenu(env, parent, -1, menumgr, msg))->drop();
 }
+
+void GUIMainMenu::updateGuiServerList()
+{
+       gui::IGUIListBox *serverlist = (gui::IGUIListBox *)getElementFromId(GUI_ID_SERVERLIST);
+       serverlist->clear();
+
+       for(std::vector<ServerListSpec>::iterator i = m_data->servers.begin();
+               i != m_data->servers.end(); i++)
+       {
+               std::string text;
+               if (i->name != "" && i->description != "")
+                       text = i->name + " (" + i->description + ")";
+               else if (i->name !="")
+                       text = i->name;
+               else
+                       text = i->address + ":" + i->port;
+
+               serverlist->addItem(narrow_to_wide(text).c_str());
+       }
+}
+
+void GUIMainMenu::serverListOnSelected()
+{
+       if (!m_data->servers.empty())
+       {
+               gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST);
+               u16 id = serverlist->getSelected();
+               if (id < 0) return;
+               ((gui::IGUIEditBox*)getElementFromId(GUI_ID_ADDRESS_INPUT))
+               ->setText(narrow_to_wide(m_data->servers[id].address).c_str());
+               ((gui::IGUIEditBox*)getElementFromId(GUI_ID_PORT_INPUT))
+               ->setText(narrow_to_wide(m_data->servers[id].port).c_str());
+       }
+}
+
+ServerListSpec GUIMainMenu::getServerListSpec(std::string address, std::string port)
+{
+       ServerListSpec server;
+       server.address = address;
+       server.port = port;
+       for(std::vector<ServerListSpec>::iterator i = m_data->servers.begin();
+               i != m_data->servers.end(); i++)
+       {
+               if (i->address == address && i->port == port)
+               {
+                       server.description = i->description;
+                       server.name = i->name;
+                       break;
+               }
+       }
+       return server;
+}
index f87ad0fdb9714f56d3b7b3dcc48107edd63d2fd6..2c657cb234323d77a1b8132d2e15851f0877b418 100644 (file)
@@ -25,6 +25,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <string>
 #include <list>
 #include "subgame.h"
+#include "serverlist.h"
+
 class IGameCallback;
 
 struct MainMenuData
@@ -33,6 +35,8 @@ struct MainMenuData
        // Generic
        int selected_tab;
        // Client options
+       std::string servername;
+       std::string serverdescription;
        std::wstring address;
        std::wstring port;
        std::wstring name;
@@ -58,8 +62,11 @@ struct MainMenuData
        std::string create_world_gameid;
        bool only_refresh;
 
+       bool serverlist_show_available; // if false show local favorites only
+
        std::vector<WorldSpec> worlds;
        std::vector<SubgameSpec> games;
+       std::vector<ServerListSpec> servers;
 
        MainMenuData():
                // Generic
@@ -73,7 +80,9 @@ struct MainMenuData
                selected_world(0),
                simple_singleplayer_mode(false),
                // Actions
-               only_refresh(false)
+               only_refresh(false),
+
+               serverlist_show_available(false)
        {}
 };
 
@@ -110,12 +119,15 @@ private:
        gui::IGUIElement* parent;
        s32 id;
        IMenuManager *menumgr;
-       
+
        bool m_is_regenerating;
        v2s32 m_topleft_client;
        v2s32 m_size_client;
        v2s32 m_topleft_server;
        v2s32 m_size_server;
+       void updateGuiServerList();
+       void serverListOnSelected();
+       ServerListSpec getServerListSpec(std::string address, std::string port);
 };
 
 #endif
index 0af9d113c6241041b5725d62d9edd93beb511e9c..6f4095148569525943577df13c89374603923b4e 100644 (file)
@@ -71,6 +71,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/string.h"
 #include "subgame.h"
 #include "quicktune.h"
+#include "serverlist.h"
 
 /*
        Settings.
@@ -1581,7 +1582,7 @@ int main(int argc, char *argv[])
                                if(menudata.selected_world != -1)
                                        g_settings->set("selected_world_path",
                                                        worldspecs[menudata.selected_world].path);
-                               
+
                                // Break out of menu-game loop to shut down cleanly
                                if(device->run() == false || kill == true)
                                        break;
@@ -1598,6 +1599,15 @@ int main(int argc, char *argv[])
                                        current_address = "";
                                        current_port = 30011;
                                }
+                               else if (address != "")
+                               {
+                                       ServerListSpec server;
+                                       server.name = menudata.servername;
+                                       server.address = wide_to_narrow(menudata.address);
+                                       server.port = wide_to_narrow(menudata.port);
+                                       server.description = menudata.serverdescription;
+                                       ServerList::insert(server);
+                               }
                                
                                // Set world path to selected one
                                if(menudata.selected_world != -1){
diff --git a/src/serverlist.cpp b/src/serverlist.cpp
new file mode 100644 (file)
index 0000000..88a213d
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+Minetest-c55
+Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+
+#include "main.h" // for g_settings
+#include "settings.h"
+#include "serverlist.h"
+#include "filesys.h"
+#include "porting.h"
+#include "log.h"
+#if USE_CURL
+#include <curl/curl.h>
+#endif
+
+namespace ServerList
+{
+std::string getFilePath()
+{
+       std::string serverlist_file = g_settings->get("serverlist_file");
+
+       std::string rel_path = std::string("client") + DIR_DELIM
+               + "serverlist" + DIR_DELIM
+               + serverlist_file;
+       std::string path = porting::path_share + DIR_DELIM + rel_path;
+       return path;
+}
+
+std::vector<ServerListSpec> getLocal()
+{
+       std::string path = ServerList::getFilePath();
+       std::string liststring;
+       if(fs::PathExists(path))
+       {
+               std::ifstream istream(path.c_str(), std::ios::binary);
+               if(istream.is_open())
+               {
+                       std::ostringstream ostream;
+                       ostream << istream.rdbuf();
+                       liststring = ostream.str();
+                       istream.close();
+               }
+       }
+
+       return ServerList::deSerialize(liststring);
+}
+
+
+#if USE_CURL
+
+static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
+{
+    ((std::string*)userp)->append((char*)contents, size * nmemb);
+    return size * nmemb;
+}
+
+
+std::vector<ServerListSpec> getOnline()
+{
+       std::string liststring;
+       CURL *curl;
+
+       curl = curl_easy_init();
+       if (curl)
+       {
+               CURLcode res;
+
+               curl_easy_setopt(curl, CURLOPT_URL, g_settings->get("serverlist_url").c_str());
+               curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, ServerList::WriteCallback);
+               curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring);
+
+               res = curl_easy_perform(curl);
+               if (res != CURLE_OK)
+                       errorstream<<"Serverlist at url "<<g_settings->get("serverlist_url")<<" not found (internet connection?)"<<std::endl;
+               curl_easy_cleanup(curl);
+       }
+
+       return ServerList::deSerialize(liststring);
+}
+
+#endif
+
+/*
+       Delete a server fromt he local favorites list
+*/
+bool deleteEntry (ServerListSpec server)
+{
+       std::vector<ServerListSpec> serverlist = ServerList::getLocal();
+       for(unsigned i = 0; i < serverlist.size(); i++)
+       {
+               if  (serverlist[i].address == server.address
+               &&   serverlist[i].port    == server.port)
+               {
+                       serverlist.erase(serverlist.begin() + i);
+               }
+       }
+
+       std::string path = ServerList::getFilePath();
+       std::ofstream stream (path.c_str());
+       if (stream.is_open())
+       {
+               stream<<ServerList::serialize(serverlist);
+               return true;
+       }
+       return false;
+}
+
+/*
+       Insert a server to the local favorites list
+*/
+bool insert (ServerListSpec server)
+{
+       // Remove duplicates
+       ServerList::deleteEntry(server);
+
+       std::vector<ServerListSpec> serverlist = ServerList::getLocal();
+
+       // Insert new server at the top of the list
+       serverlist.insert(serverlist.begin(), server);
+
+       std::string path = ServerList::getFilePath();
+       std::ofstream stream (path.c_str());
+       if (stream.is_open())
+       {
+               stream<<ServerList::serialize(serverlist);
+       }
+
+       return false;
+}
+
+std::vector<ServerListSpec> deSerialize(std::string liststring)
+{
+       std::vector<ServerListSpec> serverlist;
+       std::istringstream stream(liststring);
+       std::string line;
+       while (std::getline(stream, line))
+       {
+               std::transform(line.begin(), line.end(),line.begin(), ::toupper);
+               if (line == "[SERVER]")
+               {
+                       ServerListSpec thisserver;
+                       std::getline(stream, thisserver.name);
+                       std::getline(stream, thisserver.address);
+                       std::getline(stream, thisserver.port);
+                       std::getline(stream, thisserver.description);
+                       serverlist.push_back(thisserver);
+               }
+       }
+       return serverlist;
+}
+
+std::string serialize(std::vector<ServerListSpec> serverlist)
+{
+       std::string liststring;
+       for(std::vector<ServerListSpec>::iterator i = serverlist.begin(); i != serverlist.end(); i++)
+       {
+               liststring += "[server]\n";
+               liststring += i->name + "\n";
+               liststring += i->address + "\n";
+               liststring += i->port + "\n";
+               liststring += i->description + "\n";
+               liststring += "\n";
+       }
+       return liststring;
+}
+
+} //namespace ServerList
diff --git a/src/serverlist.h b/src/serverlist.h
new file mode 100644 (file)
index 0000000..a040d53
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+Minetest-c55
+Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include <iostream>
+#include "config.h"
+
+#ifndef SERVERLIST_HEADER
+#define SERVERLIST_HEADER
+
+struct ServerListSpec
+{
+       std::string name;
+       std::string address;
+       std::string port;
+       std::string description;
+};
+
+namespace ServerList
+{
+       std::vector<ServerListSpec> getLocal();
+       #if USE_CURL
+       std::vector<ServerListSpec> getOnline();
+       #endif
+       bool deleteEntry(ServerListSpec server);
+       bool insert(ServerListSpec server);
+       std::vector<ServerListSpec> deSerialize(std::string liststring);
+       std::string serialize(std::vector<ServerListSpec>);
+} //ServerList namespace
+
+#endif