diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8e7d29ff0..ac39e43e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -275,6 +275,7 @@ set(minetest_SRCS guiDeathScreen.cpp guiChatConsole.cpp guiCreateWorld.cpp + guiConfigureWorld.cpp guiConfirmMenu.cpp client.cpp filecache.cpp diff --git a/src/guiConfigureWorld.cpp b/src/guiConfigureWorld.cpp new file mode 100644 index 000000000..a77697f8b --- /dev/null +++ b/src/guiConfigureWorld.cpp @@ -0,0 +1,668 @@ +/* +Minetest-c55 +Copyright (C) 2012 celeron55, Perttu Ahola + +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 +#include +#include + +#include "guiConfigureWorld.h" +#include "guiMessageMenu.h" +#include +#include +#include +#include +#include +#include "gettext.h" +#include "util/string.h" +#include "settings.h" +#include "filesys.h" + +enum +{ + GUI_ID_MOD_TREEVIEW = 101, + GUI_ID_ENABLED_CHECKBOX, + GUI_ID_ENABLEALL, + GUI_ID_DISABLEALL, + GUI_ID_DEPENDS_LISTBOX, + GUI_ID_RDEPENDS_LISTBOX, + GUI_ID_CANCEL, + GUI_ID_SAVE +}; + +#define QUESTIONMARK_STR L"?" +#define CHECKMARK_STR L"\411" +#define CROSS_STR L"\403" + +GUIConfigureWorld::GUIConfigureWorld(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, WorldSpec wspec): + GUIModalMenu(env, parent, id, menumgr), + m_wspec(wspec), + m_gspec(findWorldSubgame(m_wspec.path)), + m_menumgr(menumgr) +{ + //will be initialized in regenerateGUI() + m_treeview=NULL; + + // game mods + m_gamemods = flattenModTree(getModsInPath(m_gspec.gamemods_path)); + + // world mods + std::string worldmods_path = wspec.path + DIR_DELIM + "worldmods"; + m_worldmods = flattenModTree(getModsInPath(worldmods_path)); + + // fill m_addontree with add-on mods + ModSpec addons("Add-Ons"); + std::set paths = m_gspec.addon_mods_paths; + for(std::set::iterator it=paths.begin(); + it != paths.end(); ++it) + { + std::map mods = getModsInPath(*it); + addons.modpack_content.insert(mods.begin(), mods.end()); + } + m_addontree.insert(std::make_pair(addons.name,addons)); + + // expand modpacks + m_addonmods = flattenModTree(m_addontree); + + // collect reverse dependencies + for(std::map::iterator it = m_addonmods.begin(); + it != m_addonmods.end(); ++it) + { + std::string modname = (*it).first; + ModSpec mod = (*it).second; + for(std::set::iterator dep_it = mod.depends.begin(); + dep_it != mod.depends.end(); ++dep_it) + { + m_reverse_depends.insert(std::make_pair((*dep_it),modname)); + } + } + + m_settings.readConfigFile((m_wspec.path + DIR_DELIM + "world.mt").c_str()); + std::vector names = m_settings.getNames(); + + // mod_names contains the names of mods mentioned in the world.mt file + std::set mod_names; + for(std::vector::iterator it = names.begin(); + it != names.end(); ++it) + { + std::string name = *it; + if (name.compare(0,9,"load_mod_")==0) + mod_names.insert(name.substr(9)); + } + + // find new mods (installed but not mentioned in world.mt) + for(std::map::iterator it = m_addonmods.begin(); + it != m_addonmods.end(); ++it) + { + std::string modname = (*it).first; + ModSpec mod = (*it).second; + // a mod is new if it is not a modpack, and does not occur in + // mod_names + if(mod.modpack_content.empty() && + mod_names.count(modname) == 0) + m_new_mod_names.insert(modname); + } + if(!m_new_mod_names.empty()) + { + GUIMessageMenu *menu = + new GUIMessageMenu(Environment, Parent, -1, m_menumgr, + wgettext("Warning: Some mods are not configured yet.\n" + "They will be enabled by default when you save the configuration. ")); + menu->drop(); + } + + + // find missing mods (mentioned in world.mt, but not installed) + std::set missing_mods; + for(std::set::iterator it = mod_names.begin(); + it != mod_names.end(); ++it) + { + std::string modname = *it; + if(m_addonmods.count(modname) == 0) + missing_mods.insert(modname); + } + if(!missing_mods.empty()) + { + GUIMessageMenu *menu = + new GUIMessageMenu(Environment, Parent, -1, m_menumgr, + wgettext("Warning: Some configured mods are missing.\n" + "Their setting will be removed when you save the configuration. ")); + for(std::set::iterator it = missing_mods.begin(); + it != missing_mods.end(); ++it) + m_settings.remove("load_mod_"+(*it)); + menu->drop(); + } +} + +void GUIConfigureWorld::drawMenu() +{ + gui::IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver* driver = Environment->getVideoDriver(); + + video::SColor bgcolor(140,0,0,0); + driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + + gui::IGUIElement::draw(); +} + + +void GUIConfigureWorld::regenerateGui(v2u32 screensize) +{ + + /* + Remove stuff + */ + removeChildren(); + + /* + Calculate new sizes and positions + */ + core::rect rect( + screensize.X/2 - 580/2, + screensize.Y/2 - 300/2, + screensize.X/2 + 580/2, + screensize.Y/2 + 300/2 + ); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + v2s32 size = rect.getSize(); + + v2s32 topleft = v2s32(10, 10); + + /* + Add stuff + */ + changeCtype(""); + { + core::rect rect(0, 0, 200, 20); + rect += topleft; + //proper text is set below, when a mod is selected + m_modname_text = Environment->addStaticText(L"Mod: N/A", rect, false, + false, this, -1); + } + { + core::rect rect(0, 0, 200, 20); + rect += v2s32(0, 25) + topleft; + m_enabled_checkbox = + Environment->addCheckBox(false, rect, this, GUI_ID_ENABLED_CHECKBOX, + wgettext("enabled")); + m_enabled_checkbox->setVisible(false); + } + { + core::rect rect(0, 0, 85, 30); + rect = rect + v2s32(0, 25) + topleft; + m_enableall = Environment->addButton(rect, this, GUI_ID_ENABLEALL, + wgettext("Enable All")); + m_enableall->setVisible(false); + } + { + core::rect rect(0, 0, 85, 30); + rect = rect + v2s32(115, 25) + topleft; + m_disableall = Environment->addButton(rect, this, GUI_ID_DISABLEALL, + wgettext("Disable All")); + m_disableall->setVisible(false); + } + { + core::rect rect(0, 0, 200, 20); + rect += v2s32(0, 60) + topleft; + Environment->addStaticText(wgettext("depends on:"), + rect, false, false, this, -1); + } + { + core::rect rect(0, 0, 200, 85); + rect += v2s32(0, 80) + topleft; + m_dependencies_listbox = + Environment->addListBox(rect, this, GUI_ID_DEPENDS_LISTBOX, true); + } + { + core::rect rect(0, 0, 200, 20); + rect += v2s32(0, 175) + topleft; + Environment->addStaticText(wgettext("is required by:"), + rect, false, false, this, -1); + } + { + core::rect rect(0, 0, 200, 85); + rect += v2s32(0, 195) + topleft; + m_rdependencies_listbox = + Environment->addListBox(rect,this, GUI_ID_RDEPENDS_LISTBOX,true); + } + { + core::rect rect(0, 0, 340, 250); + rect += v2s32(220, 0) + topleft; + m_treeview = Environment->addTreeView(rect, this, + GUI_ID_MOD_TREEVIEW,true); + buildTreeView(m_addontree, m_treeview->getRoot()); + } + { + core::rect rect(0, 0, 120, 30); + rect = rect + v2s32(330, 270) - topleft; + Environment->addButton(rect, this, GUI_ID_CANCEL, + wgettext("Cancel")); + } + { + core::rect rect(0, 0, 120, 30); + rect = rect + v2s32(460, 270) - topleft; + Environment->addButton(rect, this, GUI_ID_SAVE, + wgettext("Save")); + } + changeCtype("C"); + + // at start, none of the treeview nodes is selected, so we select + // the first element in the treeview of mods manually here. + if(m_treeview->getRoot()->hasChilds()) + { + m_treeview->getRoot()->getFirstChild()->setExpanded(true); + m_treeview->getRoot()->getFirstChild()->setSelected(true); + // Because a manual ->setSelected() doesn't cause an event, we + // have to do this here: + adjustSidebar(); + } +} + +bool GUIConfigureWorld::OnEvent(const SEvent& event) +{ + + gui::IGUITreeViewNode* selected_node = NULL; + if(m_treeview != NULL) + selected_node = m_treeview->getSelected(); + + if(event.EventType==EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown) + { + switch (event.KeyInput.Key) { + case KEY_ESCAPE: { + quitMenu(); + return true; + } + // irrlicht's built-in TreeView gui has no keyboard control, + // so we do it here: up/down to select prev/next node, + // left/right to collapse/expand nodes, space to toggle + // enabled/disabled. + case KEY_DOWN: { + if(selected_node != NULL) + { + gui::IGUITreeViewNode* node = selected_node->getNextVisible(); + if(node != NULL) + { + node->setSelected(true); + adjustSidebar(); + } + } + return true; + } + case KEY_UP: { + if(selected_node != NULL) + { + gui::IGUITreeViewNode* node = selected_node->getPrevSibling(); + if(node!=NULL) + { + node->setSelected(true); + adjustSidebar(); + } + else + { + gui::IGUITreeViewNode* parent = selected_node->getParent(); + if(selected_node == parent->getFirstChild() && + parent != m_treeview->getRoot()) + { + parent->setSelected(true); + adjustSidebar(); + } + } + } + return true; + } + case KEY_RIGHT: { + if(selected_node != NULL && selected_node->hasChilds()) + selected_node->setExpanded(true); + return true; + } + case KEY_LEFT: { + if(selected_node != NULL && selected_node->hasChilds()) + selected_node->setExpanded(false); + return true; + } + case KEY_SPACE: { + if(selected_node != NULL && !selected_node->hasChilds() && + selected_node->getText() != NULL) + { + std::string modname = wide_to_narrow(selected_node->getText()); + bool checked = m_enabled_checkbox->isChecked(); + m_enabled_checkbox->setChecked(!checked); + setEnabled(modname,!checked); + } + return true; + } + default: {} + } + } + if(event.EventType==EET_GUI_EVENT) + { + if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST + && isVisible()) + { + if(!canTakeFocus(event.GUIEvent.Element)) + { + dstream<<"GUIConfigureWorld: Not allowing focus change." + <getID()){ + case GUI_ID_CANCEL: { + quitMenu(); + return true; + } + case GUI_ID_SAVE: { + for(std::set::iterator it = m_new_mod_names.begin(); + it!= m_new_mod_names.end(); ++it) + { + m_settings.setBool("load_mod_"+(*it),true); + } + std::string worldmtfile = m_wspec.path+DIR_DELIM+"world.mt"; + m_settings.updateConfigFile(worldmtfile.c_str()); + + // The trailing spaces are because there seems to be a + // bug in the text-size calculation. if the trailing + // spaces are removed from the message text, the + // message gets wrapped and parts of it are cut off: + GUIMessageMenu *menu = + new GUIMessageMenu(Environment, Parent, -1, m_menumgr, + wgettext("Configuration saved. ")); + menu->drop(); + + ModConfiguration modconf(m_wspec.path); + if(!modconf.isConsistent()) + { + GUIMessageMenu *menu = + new GUIMessageMenu(Environment, Parent, -1, m_menumgr, + wgettext("Warning: Configuration not consistent. ")); + menu->drop(); + } + + quitMenu(); + return true; + } + case GUI_ID_ENABLEALL: { + if(selected_node != NULL && selected_node->getText() != NULL) + { + std::string modname = wide_to_narrow(selected_node->getText()); + ModSpec mod = m_addonmods[modname]; + enableAllMods(mod.modpack_content,true); + } + return true; + } + case GUI_ID_DISABLEALL: { + if(selected_node != NULL && selected_node->getText() != NULL) + { + std::string modname = wide_to_narrow(selected_node->getText()); + ModSpec mod = m_addonmods[modname]; + enableAllMods(mod.modpack_content,false); + } + return true; + } + } + } + if(event.GUIEvent.EventType==gui::EGET_CHECKBOX_CHANGED && + event.GUIEvent.Caller->getID() == GUI_ID_ENABLED_CHECKBOX) + { + if(selected_node != NULL && !selected_node->hasChilds() && + selected_node->getText() != NULL) + { + std::string modname = wide_to_narrow(selected_node->getText()); + setEnabled(modname, m_enabled_checkbox->isChecked()); + } + return true; + } + if(event.GUIEvent.EventType==gui::EGET_TREEVIEW_NODE_SELECT && + event.GUIEvent.Caller->getID() == GUI_ID_MOD_TREEVIEW) + { + selecting_dep = -1; + selecting_rdep = -1; + adjustSidebar(); + return true; + } + if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED && + event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX) + { + selecting_dep = m_dependencies_listbox->getSelected(); + selecting_rdep = -1; + return true; + } + if(event.GUIEvent.EventType==gui::EGET_LISTBOX_CHANGED && + event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX) + { + selecting_dep = -1; + selecting_rdep = m_rdependencies_listbox->getSelected(); + return true; + } + + //double click in a dependency listbox: find corresponding + //treeviewnode and select it: + if(event.GUIEvent.EventType==gui::EGET_LISTBOX_SELECTED_AGAIN) + { + gui::IGUIListBox* box = NULL; + if(event.GUIEvent.Caller->getID() == GUI_ID_DEPENDS_LISTBOX) + { + box = m_dependencies_listbox; + if(box->getSelected() != selecting_dep) + return true; + } + if(event.GUIEvent.Caller->getID() == GUI_ID_RDEPENDS_LISTBOX) + { + box = m_rdependencies_listbox; + if(box->getSelected() != selecting_rdep) + return true; + } + if(box != NULL && box->getSelected() != -1 && + box->getListItem(box->getSelected()) != NULL) + { + std::string modname = + wide_to_narrow(box->getListItem(box->getSelected())); + std::map::iterator it = + m_nodes.find(modname); + if(it != m_nodes.end()) + { + // select node and make sure node is visible by + // expanding all parents + gui::IGUITreeViewNode* node = (*it).second; + node->setSelected(true); + while(!node->isVisible() && + node->getParent() != m_treeview->getRoot()) + { + node = node->getParent(); + node->setExpanded(true); + } + adjustSidebar(); + } + } + return true; + } + } + + return Parent ? Parent->OnEvent(event) : false; +} + +void GUIConfigureWorld::buildTreeView(std::map mods, + gui::IGUITreeViewNode* node) +{ + for(std::map::iterator it = mods.begin(); + it != mods.end(); ++it) + { + std::string modname = (*it).first; + ModSpec mod = (*it).second; + gui::IGUITreeViewNode* new_node = + node->addChildBack(narrow_to_wide(modname).c_str()); + m_nodes.insert(std::make_pair(modname, new_node)); + if(!mod.modpack_content.empty()) + buildTreeView(mod.modpack_content, new_node); + else + { + // set icon for node: ? for new mods, x for disabled mods, + // checkmark for enabled mods + if(m_new_mod_names.count(modname) > 0) + { + new_node->setIcon(QUESTIONMARK_STR); + } + else + { + bool mod_enabled = true; + if(m_settings.exists("load_mod_"+modname)) + mod_enabled = m_settings.getBool("load_mod_"+modname); + if(mod_enabled) + new_node->setIcon(CHECKMARK_STR); + else + new_node->setIcon(CROSS_STR); + } + } + } +} + + +void GUIConfigureWorld::adjustSidebar() +{ + gui::IGUITreeViewNode* node = m_treeview->getSelected(); + std::wstring modname_w; + if(node->getText() != NULL) + modname_w = node->getText(); + else + modname_w = L"N/A"; + std::string modname = wide_to_narrow(modname_w); + + // if modpack, show enable/disable all buttons. otherwise, show + // enabled checkbox + if(node->hasChilds()) + { + m_enabled_checkbox->setVisible(false); + m_disableall->setVisible(true); + m_enableall->setVisible(true); + m_modname_text->setText((L"Modpack: "+modname_w).c_str()); + } + else + { + m_disableall->setVisible(false); + m_enableall->setVisible(false); + m_enabled_checkbox->setVisible(true); + m_modname_text->setText((L"Mod: "+modname_w).c_str()); + } + + // the mod is enabled unless it is disabled in the world.mt settings. + bool mod_enabled = true; + if(m_settings.exists("load_mod_"+modname)) + mod_enabled = m_settings.getBool("load_mod_"+modname); + m_enabled_checkbox->setChecked(mod_enabled); + + // dependencies of this mod: + m_dependencies_listbox->clear(); + ModSpec mspec = m_addonmods[modname]; + for(std::set::iterator it=mspec.depends.begin(); + it != mspec.depends.end(); ++it) + { + // check if it is an add-on mod or a game/world mod. We only + // want to show add-ons + std::string dependency = (*it); + if(m_gamemods.count(dependency) > 0) + dependency += " (" + m_gspec.id + ")"; + else if(m_worldmods.count(dependency) > 0) + dependency += " (" + m_wspec.name + ")"; + else if(m_addonmods.count(dependency) == 0) + dependency += " (missing)"; + m_dependencies_listbox->addItem(narrow_to_wide(dependency).c_str()); + } + + + // reverse dependencies of this mod: + m_rdependencies_listbox->clear(); + std::pair< std::multimap::iterator, + std::multimap::iterator > rdep = + m_reverse_depends.equal_range(modname); + for(std::multimap::iterator it = rdep.first; + it != rdep.second; ++it) + { + // check if it is an add-on mod or a game/world mod. We only + // want to show add-ons + std::string rdependency = (*it).second; + if(m_addonmods.count(rdependency) > 0) + m_rdependencies_listbox->addItem(narrow_to_wide(rdependency).c_str()); + } +} + +void GUIConfigureWorld::enableAllMods(std::map mods,bool enable) +{ + for(std::map::iterator it = mods.begin(); + it != mods.end(); ++it) + { + ModSpec mod = (*it).second; + if(!mod.modpack_content.empty()) + // a modpack, recursively enable all mods in it + enableAllMods(mod.modpack_content,enable); + else // not a modpack + setEnabled(mod.name, enable); + } +} + +void GUIConfigureWorld::enableMod(std::string modname) +{ + m_settings.setBool("load_mod_"+modname,true); + std::map::iterator it = + m_nodes.find(modname); + if(it != m_nodes.end()) + (*it).second->setIcon(CHECKMARK_STR); + m_new_mod_names.erase(modname); + //also enable all dependencies + ModSpec mspec = m_addonmods[modname]; + for(std::set::iterator it=mspec.depends.begin(); + it != mspec.depends.end(); ++it) + { + std::string dependency = *it; + // only enable it if it is an add-on mod + if(m_addonmods.count(dependency) > 0) + enableMod(dependency); + } +} + +void GUIConfigureWorld::disableMod(std::string modname) +{ + m_settings.setBool("load_mod_"+modname,false); + std::map::iterator it = + m_nodes.find(modname); + if(it != m_nodes.end()) + (*it).second->setIcon(CROSS_STR); + m_new_mod_names.erase(modname); + //also disable all mods that depend on this one + std::pair::iterator, + std::multimap::iterator > rdep = + m_reverse_depends.equal_range(modname); + for(std::multimap::iterator it = rdep.first; + it != rdep.second; ++it) + { + std::string rdependency = (*it).second; + // only disable it if it is an add-on mod + if(m_addonmods.count(rdependency) > 0) + disableMod(rdependency); + } +} + diff --git a/src/guiConfigureWorld.h b/src/guiConfigureWorld.h new file mode 100644 index 000000000..2280c6dbe --- /dev/null +++ b/src/guiConfigureWorld.h @@ -0,0 +1,110 @@ +/* +Minetest-c55 +Copyright (C) 2012 celeron55, Perttu Ahola + +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. +*/ + +#ifndef GUICONFIGUREWORLD_HEADER +#define GUICONFIGUREWORLD_HEADER + +#include "irrlichttypes_extrabloated.h" +#include "modalMenu.h" +#include "mods.h" +#include "subgame.h" +#include "settings.h" + + +namespace irr{ + namespace gui{ + class IGUITreeViewNode; + } +} + +class GUIConfigureWorld : public GUIModalMenu +{ +public: + GUIConfigureWorld(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, WorldSpec wspec); + + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + bool OnEvent(const SEvent& event); + +private: + WorldSpec m_wspec; + SubgameSpec m_gspec; + + // tree of installed add-on mods. key is the mod name, modpacks + // are not expanded. + std::map m_addontree; + + // like m_addontree, but modpacks are expanded. + std::map m_addonmods; + + // list of game mods (flattened) + std::map m_gamemods; + + // list of world mods (flattened) + std::map m_worldmods; + + // for each mod, the set of mods depending on it + std::multimap m_reverse_depends; + + // the settings in the world.mt file + Settings m_settings; + + // mods that are installed but not mentioned in world.mt file + std::set m_new_mod_names; + + // maps modnames to nodes in m_treeview + std::map m_nodes; + + gui::IGUIStaticText* m_modname_text; + gui::IGUITreeView* m_treeview; + gui::IGUIButton* m_enableall; + gui::IGUIButton* m_disableall; + gui::IGUICheckBox* m_enabled_checkbox; + gui::IGUIListBox* m_dependencies_listbox; + gui::IGUIListBox* m_rdependencies_listbox; + void buildTreeView(std::map mods, + gui::IGUITreeViewNode* node); + void adjustSidebar(); + void enableAllMods(std::map mods, bool enable); + void setEnabled(std::string modname, bool enable) + { + if(enable) + enableMod(modname); + else + disableMod(modname); + }; + + void enableMod(std::string modname); + void disableMod(std::string modname); + + // hack to work around wonky handling of double-click in + // irrlicht. store selected index of listbox items here so event + // handling can check whether it was a real double click on the + // same item. (irrlicht also reports a double click if you rapidly + // select two different items.) + int selecting_dep; + int selecting_rdep; + + IMenuManager* m_menumgr; +}; +#endif diff --git a/src/guiMainMenu.cpp b/src/guiMainMenu.cpp index ca0c1317c..77a5a85b8 100644 --- a/src/guiMainMenu.cpp +++ b/src/guiMainMenu.cpp @@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiMainMenu.h" #include "guiKeyChangeMenu.h" #include "guiCreateWorld.h" +#include "guiConfigureWorld.h" #include "guiMessageMenu.h" #include "guiConfirmMenu.h" #include "debug.h" @@ -1033,11 +1034,22 @@ bool GUIMainMenu::OnEvent(const SEvent& event) return true; } case GUI_ID_CONFIGURE_WORLD_BUTTON: { - GUIMessageMenu *menu = new GUIMessageMenu(env, parent, - -1, menumgr, - wgettext("Nothing here")); - menu->drop(); - return true; + MainMenuData cur; + readInput(&cur); + if(cur.selected_world == -1) + { + (new GUIMessageMenu(env, parent, -1, menumgr, + wgettext("Cannot configure world: Nothing selected")) + )->drop(); + } + else + { + WorldSpec wspec = m_data->worlds[cur.selected_world]; + GUIConfigureWorld *menu = new GUIConfigureWorld(env, parent, + -1, menumgr, wspec); + menu->drop(); + return true; + } } case GUI_ID_SERVERLIST_DELETE: { gui::IGUIListBox *serverlist = (gui::IGUIListBox*)getElementFromId(GUI_ID_SERVERLIST); diff --git a/src/mods.cpp b/src/mods.cpp index 08e8e276f..9e3c7d5d7 100644 --- a/src/mods.cpp +++ b/src/mods.cpp @@ -18,23 +18,16 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "mods.h" -#include -#include -#include -#include #include "filesys.h" #include "strfnd.h" #include "log.h" +#include "subgame.h" +#include "settings.h" -static void collectMods(const std::string &modspath, - std::queue &mods_satisfied, - core::list &mods_unsorted, - std::map &mod_names, - std::string indentation) +std::map getModsInPath(std::string path) { - TRACESTREAM(< dirlist = fs::GetDirListing(modspath); + std::map result; + std::vector dirlist = fs::GetDirListing(path); for(u32 j=0; j::const_iterator i; - i = mod_names.find(modname); - if(i != mod_names.end()){ - std::string s; - infostream<second< depends; - std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(), + else // not a modpack, add the modspec + { + std::set depends; + std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(), std::ios_base::binary); - while(is.good()){ - std::string dep; - std::getline(is, dep); - dep = trim(dep); - if(dep != "") - depends.insert(dep); + while(is.good()) + { + std::string dep; + std::getline(is, dep); + dep = trim(dep); + if(dep != "") + depends.insert(dep); + } + + ModSpec spec(modname, modpath, depends); + result.insert(std::make_pair(modname,spec)); } - ModSpec spec(modname, modpath, depends); - mods_unsorted.push_back(spec); - if(depends.empty()) - mods_satisfied.push(spec); - mod_names[modname] = modpath; } + return result; } -// Get a dependency-sorted list of ModSpecs -core::list getMods(core::list &modspaths) - throw(ModError) +std::map flattenModTree(std::map mods) { - std::queue mods_satisfied; - core::list mods_unsorted; - core::list mods_sorted; - // name, path: For detecting name conflicts - std::map mod_names; - for(core::list::Iterator i = modspaths.begin(); - i != modspaths.end(); i++){ - std::string modspath = *i; - collectMods(modspath, mods_satisfied, mods_unsorted, mod_names, ""); - } - // Sort by depencencies - while(!mods_satisfied.empty()){ - ModSpec mod = mods_satisfied.front(); - mods_satisfied.pop(); - mods_sorted.push_back(mod); - for(core::list::Iterator i = mods_unsorted.begin(); - i != mods_unsorted.end(); i++){ - ModSpec &mod2 = *i; - if(mod2.unsatisfied_depends.empty()) - continue; - mod2.unsatisfied_depends.erase(mod.name); - if(!mod2.unsatisfied_depends.empty()) - continue; - mods_satisfied.push(mod2); + std::map result; + for(std::map::iterator it = mods.begin(); + it != mods.end(); ++it) + { + ModSpec mod = (*it).second; + if(!mod.modpack_content.empty()) //is a modpack + { + std::map content = + flattenModTree(mod.modpack_content); + result.insert(content.begin(),content.end()); + result.insert(std::make_pair(mod.name,mod)); + } + else //not a modpack + { + result.insert(std::make_pair(mod.name,mod)); } } - std::ostringstream errs(std::ios::binary); - // Check unsatisfied dependencies - for(core::list::Iterator i = mods_unsorted.begin(); - i != mods_unsorted.end(); i++){ - ModSpec &mod = *i; - if(mod.unsatisfied_depends.empty()) - continue; - errs<<"mod \""<::iterator - i = mod.unsatisfied_depends.begin(); - i != mod.unsatisfied_depends.end(); i++){ - errs<<" \""<<(*i)<<"\""; + return result; +} + +std::vector flattenMods(std::map mods) +{ + std::vector result; + for(std::map::iterator it = mods.begin(); + it != mods.end(); ++it) + { + ModSpec mod = (*it).second; + if(!mod.modpack_content.empty()) //is a modpack + { + std::vector content = flattenMods(mod.modpack_content); + result.reserve(result.size() + content.size()); + result.insert(result.end(),content.begin(),content.end()); + + } + else //not a modpack + { + // infostream << "inserting mod " << mod.name << std::endl; + result.push_back(mod); } - errs<<"."<::Iterator i = mods_sorted.begin(); - i != mods_sorted.end(); i++){ - ModSpec &mod = *i; - TRACESTREAM(<<"name=\""<::iterator i = mod.depends.begin(); - i != mod.depends.end(); i++) - TRACESTREAM(<<" "<<(*i)); - TRACESTREAM(<<" ] unsatisfied_depends=["); - for(std::set::iterator i = mod.unsatisfied_depends.begin(); - i != mod.unsatisfied_depends.end(); i++) - TRACESTREAM(<<" "<<(*i)); - TRACESTREAM(<<" ]"< filterMods(std::vector mods, + std::set exclude_mod_names) +{ + std::vector result; + for(std::vector::iterator it = mods.begin(); + it != mods.end(); ++it) + { + ModSpec& mod = *it; + if(exclude_mod_names.count(mod.name) == 0) + result.push_back(mod); + } + return result; +} + +void ModConfiguration::addModsInPathFiltered(std::string path, std::set exclude_mods) +{ + addMods(filterMods(flattenMods(getModsInPath(path)),exclude_mods)); } +void ModConfiguration::addMods(std::vector new_mods) +{ + // Step 1: remove mods in sorted_mods from unmet dependencies + // of new_mods. new mods without unmet dependencies are + // temporarily stored in satisfied_mods + std::vector satisfied_mods; + for(std::vector::iterator it = m_sorted_mods.begin(); + it != m_sorted_mods.end(); ++it) + { + ModSpec mod = *it; + for(std::vector::iterator it_new = new_mods.begin(); + it_new != new_mods.end(); ++it_new) + { + ModSpec& mod_new = *it_new; + //infostream << "erasing dependency " << mod.name << " from " << mod_new.name << std::endl; + mod_new.unsatisfied_depends.erase(mod.name); + } + } + + // split new mods into satisfied and unsatisfied + for(std::vector::iterator it = new_mods.begin(); + it != new_mods.end(); ++it) + { + ModSpec mod_new = *it; + if(mod_new.unsatisfied_depends.empty()) + satisfied_mods.push_back(mod_new); + else + m_unsatisfied_mods.push_back(mod_new); + } + + // Step 2: mods without unmet dependencies can be appended to + // the sorted list. + while(!satisfied_mods.empty()) + { + ModSpec mod = satisfied_mods.back(); + m_sorted_mods.push_back(mod); + satisfied_mods.pop_back(); + for(std::list::iterator it = m_unsatisfied_mods.begin(); + it != m_unsatisfied_mods.end(); ) + { + ModSpec& mod2 = *it; + mod2.unsatisfied_depends.erase(mod.name); + if(mod2.unsatisfied_depends.empty()) + { + satisfied_mods.push_back(mod2); + it = m_unsatisfied_mods.erase(it); + } + else + ++it; + } + } +} + +ModConfiguration::ModConfiguration(std::string worldpath) +{ + // Add all world mods and all game mods + addModsInPath(worldpath + DIR_DELIM + "worldmods"); + SubgameSpec gamespec = findWorldSubgame(worldpath); + addModsInPath(gamespec.gamemods_path); + + // check world.mt file for mods explicitely declared to be + // loaded or not by a load_mod_ = ... line. + std::string worldmt = worldpath+DIR_DELIM+"world.mt"; + Settings worldmt_settings; + worldmt_settings.readConfigFile(worldmt.c_str()); + std::vector names = worldmt_settings.getNames(); + std::set exclude_mod_names; + for(std::vector::iterator it = names.begin(); + it != names.end(); ++it) + { + std::string name = *it; + // for backwards compatibility: exclude only mods which are + // explicitely excluded. if mod is not mentioned at all, it is + // enabled. So by default, all installed mods are enabled. + if (name.compare(0,9,"load_mod_") == 0 && + !worldmt_settings.getBool(name)) + { + exclude_mod_names.insert(name.substr(9)); + } + } + + for(std::set::const_iterator i = gamespec.addon_mods_paths.begin(); + i != gamespec.addon_mods_paths.end(); ++i) + addModsInPathFiltered((*i),exclude_mod_names); +} diff --git a/src/mods.h b/src/mods.h index d55dd6c92..37e2cd2db 100644 --- a/src/mods.h +++ b/src/mods.h @@ -22,8 +22,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes.h" #include +#include #include +#include #include +#include #include class ModError : public std::exception @@ -47,21 +50,93 @@ struct ModSpec { std::string name; std::string path; + //if normal mod: std::set depends; std::set unsatisfied_depends; - - ModSpec(const std::string &name_="", const std::string path_="", - const std::set &depends_=std::set()): + // if modpack: + std::map modpack_content; + ModSpec(const std::string name_="", const std::string path_="", + const std::set depends_=std::set()): name(name_), path(path_), depends(depends_), - unsatisfied_depends(depends_) + unsatisfied_depends(depends_), + modpack_content() {} }; -// Get a dependency-sorted list of ModSpecs -core::list getMods(core::list &modspaths) - throw(ModError); + +std::map getModsInPath(std::string path); + +// expands modpack contents, but does not replace them. +std::map flattenModTree(std::map mods); + +// replaces modpack Modspecs with their content +std::vector flattenMods(std::map mods); + +// removes Mods mentioned in exclude_mod_names +std::vector filterMods(std::vector mods, + std::set exclude_mod_names); + +// a ModConfiguration is a subset of installed mods, expected to have +// all dependencies fullfilled, so it can be used as a list of mods to +// load when the game starts. +class ModConfiguration +{ +public: + ModConfiguration(): + m_unsatisfied_mods(), + m_sorted_mods() + {} + + + ModConfiguration(std::string worldpath); + + // adds all mods in the given path. used for games, modpacks + // and world-specific mods (worldmods-folders) + void addModsInPath(std::string path) + { + addMods(flattenMods(getModsInPath(path))); + } + + // adds all mods in the given path whose name does not appear + // in the exclude_mods set. + void addModsInPathFiltered(std::string path, std::set exclude_mods); + + // adds all mods in the set. + void addMods(std::vector mods); + + // checks if all dependencies are fullfilled. + bool isConsistent() + { + return m_unsatisfied_mods.empty(); + } + + std::vector getMods() + { + return m_sorted_mods; + } + + std::list getUnsatisfiedMods() + { + return m_unsatisfied_mods; + } + +private: + + // mods with unmet dependencies. This is a list and not a + // vector because we want easy removal of elements at every + // position. + std::list m_unsatisfied_mods; + + // list of mods sorted such that they can be loaded in the + // given order with all dependencies being fullfilled. I.e., + // every mod in this list has only dependencies on mods which + // appear earlier in the vector. + std::vector m_sorted_mods; + +}; + #endif diff --git a/src/server.cpp b/src/server.cpp index 0a8288324..853d0a486 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -980,33 +980,65 @@ Server::Server( std::string rollback_path = m_path_world+DIR_DELIM+"rollback.txt"; m_rollback = createRollbackManager(rollback_path, this); - // Add world mod search path - m_modspaths.push_front(m_path_world + DIR_DELIM + "worldmods"); - // Add addon mod search path - for(std::set::const_iterator i = m_gamespec.mods_paths.begin(); - i != m_gamespec.mods_paths.end(); i++) - m_modspaths.push_front((*i)); - - // Print out mod search paths - for(core::list::Iterator i = m_modspaths.begin(); - i != m_modspaths.end(); i++){ - std::string modspath = *i; - infostream<<"- mods: "< modlist = modconf.getUnsatisfiedMods(); + for(std::list::iterator it = modlist.begin(); + it != modlist.end(); ++it) + { + errorstream << (*it).name << " "; + } + errorstream << std::endl; + } + + Settings worldmt_settings; + std::string worldmt = m_path_world + DIR_DELIM + "world.mt"; + worldmt_settings.readConfigFile(worldmt.c_str()); + std::vector names = worldmt_settings.getNames(); + std::set exclude_mod_names; + std::set load_mod_names; + for(std::vector::iterator it = names.begin(); + it != names.end(); ++it) + { + std::string name = *it; + if (name.compare(0,9,"load_mod_")==0) + { + if(worldmt_settings.getBool(name)) + load_mod_names.insert(name.substr(9)); + else + exclude_mod_names.insert(name.substr(9)); + } + } + // complain about mods declared to be loaded, but not found + for(std::vector::iterator it = m_mods.begin(); + it != m_mods.end(); ++it) + load_mod_names.erase((*it).name); + if(!load_mod_names.empty()) + { + errorstream << "The following mods could not be found: "; + for(std::set::iterator it = load_mod_names.begin(); + it != load_mod_names.end(); ++it) + errorstream << (*it) << " "; + errorstream << std::endl; + } + + // Path to builtin.lua + std::string builtinpath = getBuiltinLuaPath() + DIR_DELIM + "builtin.lua"; + // Lock environment JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); // Initialize scripting - + infostream<<"Server: Initializing Lua"<::Iterator i = m_mods.begin(); + for(std::vector::iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; infostream<::Iterator i = m_mods.begin(); + for(std::vector::iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; std::string scriptpath = mod.path + DIR_DELIM + "init.lua"; @@ -4105,7 +4135,7 @@ void Server::fillMediaCache() // Collect all media file paths std::list paths; - for(core::list::Iterator i = m_mods.begin(); + for(std::vector::iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; paths.push_back(mod.path + DIR_DELIM + "textures"); @@ -4770,7 +4800,7 @@ IWritableCraftDefManager* Server::getWritableCraftDefManager() const ModSpec* Server::getModSpec(const std::string &modname) { - for(core::list::Iterator i = m_mods.begin(); + for(std::vector::iterator i = m_mods.begin(); i != m_mods.end(); i++){ const ModSpec &mod = *i; if(mod.name == modname) @@ -4780,7 +4810,7 @@ const ModSpec* Server::getModSpec(const std::string &modname) } void Server::getModNames(core::list &modlist) { - for(core::list::Iterator i = m_mods.begin(); i != m_mods.end(); i++) + for(std::vector::iterator i = m_mods.begin(); i != m_mods.end(); i++) { modlist.push_back((*i).name); } diff --git a/src/server.h b/src/server.h index 43beb167a..26e47d36c 100644 --- a/src/server.h +++ b/src/server.h @@ -756,7 +756,7 @@ private: EventManager *m_event; // Mods - core::list m_mods; + std::vector m_mods; /* Threads diff --git a/src/settings.h b/src/settings.h index 1ab6fcc6b..1a29ef00a 100644 --- a/src/settings.h +++ b/src/settings.h @@ -71,6 +71,26 @@ public: os< getNames(){ + std::vector names; + for(core::map::Iterator + i = m_settings.getIterator(); + i.atEnd() == false; i++) + { + std::string name = i.getNode()->getKey(); + names.push_back(name); + } + return names; + } + + // remove a setting + bool remove(const std::string& name) + { + return m_settings.remove(name); + } + bool parseConfigLine(const std::string &line) { diff --git a/src/subgame.cpp b/src/subgame.cpp index c36189933..2f5340373 100644 --- a/src/subgame.cpp +++ b/src/subgame.cpp @@ -74,9 +74,9 @@ SubgameSpec findSubgame(const std::string &id) } if(game_path == "") return SubgameSpec(); + std::string gamemod_path = game_path + DIR_DELIM + "mods"; // Find mod directories std::set mods_paths; - mods_paths.insert(game_path + DIR_DELIM + "mods"); if(!user_game) mods_paths.insert(share + DIR_DELIM + "mods" + DIR_DELIM + id); if(user != share || user_game) @@ -84,7 +84,7 @@ SubgameSpec findSubgame(const std::string &id) std::string game_name = getGameName(game_path); if(game_name == "") game_name = id; - return SubgameSpec(id, game_path, mods_paths, game_name); + return SubgameSpec(id, game_path, gamemod_path, mods_paths, game_name); } SubgameSpec findWorldSubgame(const std::string &world_path) @@ -96,7 +96,7 @@ SubgameSpec findWorldSubgame(const std::string &world_path) SubgameSpec gamespec; gamespec.id = world_gameid; gamespec.path = world_gamepath; - gamespec.mods_paths.insert(world_gamepath + DIR_DELIM + "mods"); + gamespec.gamemods_path= world_gamepath + DIR_DELIM + "mods"; gamespec.name = getGameName(world_gamepath); if(gamespec.name == "") gamespec.name = "unknown"; diff --git a/src/subgame.h b/src/subgame.h index bffa86e28..dd725caf7 100644 --- a/src/subgame.h +++ b/src/subgame.h @@ -29,17 +29,20 @@ with this program; if not, write to the Free Software Foundation, Inc., struct SubgameSpec { std::string id; // "" = game does not exist - std::string path; - std::set mods_paths; + std::string path; // path to game + std::string gamemods_path; //path to mods of the game + std::set addon_mods_paths; //paths to addon mods for this game std::string name; SubgameSpec(const std::string &id_="", - const std::string &path_="", - const std::set &mods_paths_=std::set(), + const std::string &path_="", + const std::string &gamemods_path_="", + const std::set &addon_mods_paths_=std::set(), const std::string &name_=""): id(id_), path(path_), - mods_paths(mods_paths_), + gamemods_path(gamemods_path_), + addon_mods_paths(addon_mods_paths_), name(name_) {}