Basic support for configuring which mods to load for each world
authorJürgen Doser <jurgen.doser@gmail.com>
Sat, 8 Dec 2012 17:10:54 +0000 (18:10 +0100)
committerPerttu Ahola <celeron55@gmail.com>
Mon, 21 Jan 2013 15:31:50 +0000 (17:31 +0200)
settings.h: added function to return all keys used in settings, and a
function to remove a setting

mods.{h,cpp}: added class ModConfiguration that represents a subset of the installed mods.

server.{h,cpp}: server does not load add-on mods that are disabled in
the world.mt file. mods are disabled by a setting of the form
"load_mod_<modname> = false". if no load_mod_<modname> = ... setting
is found, the mod is loaded anyways for backwards compatibilty. server
also complains to errorstream about mods with unstatisfied
dependencies and about mods that are not installed.

guiConfigureWorld.{h,cpp}: shows a treeview of installed add-on mods
and modpacks with little icons in front of their name indicating their
status: a checkmark for enabled mods, a cross for disabled mods, a
question mark for "new" mods

Mods can be enabled/disabled by a checkbox. Mods also show a list of
dependencies and reverse dependencies. double-click on a mod in
dependency or reverse dependency listbox selects the corresponding
mod. Enabling a mod also enables all its dependencies. Disabling a mod
also disables all its reverse dependencies.

For modpacks, show buttons to enable/disable all mods (recursively,
including their dependencies) in it.

Button "Save" saves the current settings to the world.mt file and
returns to the main menu. Button "Cancel" returns to main menu without
saving.

basic keyboard controls (if the proper widget has keyboard focus):

up/down: scroll through tree of mods
left/right: collaps/expand a modpack
space: enable/disable the selected mod

src/CMakeLists.txt
src/guiConfigureWorld.cpp [new file with mode: 0644]
src/guiConfigureWorld.h [new file with mode: 0644]
src/guiMainMenu.cpp
src/mods.cpp
src/mods.h
src/server.cpp
src/server.h
src/settings.h
src/subgame.cpp
src/subgame.h

index 8e7d29ff0703e30057ea3132ccfaa61b2032b8c9..ac39e43e3d91465a02835bfdddeb6987e05b9488 100644 (file)
@@ -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 (file)
index 0000000..a77697f
--- /dev/null
@@ -0,0 +1,668 @@
+/*
+Minetest-c55
+Copyright (C) 2012 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 <string>
+#include <map>
+
+#include "guiConfigureWorld.h"
+#include "guiMessageMenu.h"
+#include <IGUIButton.h>
+#include <IGUICheckBox.h>
+#include <IGUIListBox.h>
+#include <IGUIStaticText.h>
+#include <IGUITreeView.h>
+#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<std::string> paths = m_gspec.addon_mods_paths;
+       for(std::set<std::string>::iterator it=paths.begin();
+               it != paths.end(); ++it)
+       {
+               std::map<std::string,ModSpec> 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<std::string, ModSpec>::iterator it = m_addonmods.begin();
+               it != m_addonmods.end(); ++it)
+       {
+               std::string modname = (*it).first;
+               ModSpec mod = (*it).second;
+               for(std::set<std::string>::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<std::string> names = m_settings.getNames();
+
+       // mod_names contains the names of mods mentioned in the world.mt file
+       std::set<std::string> mod_names;
+       for(std::vector<std::string>::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<std::string, ModSpec>::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<std::string> missing_mods;
+       for(std::set<std::string>::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<std::string>::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<s32> 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<s32> 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<s32> 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<s32> 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<s32> 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<s32> rect(0, 0, 200, 20);
+               rect += v2s32(0, 60) + topleft;
+               Environment->addStaticText(wgettext("depends on:"),
+                       rect, false, false, this, -1);
+       }
+       {
+               core::rect<s32> rect(0, 0, 200, 85);
+               rect += v2s32(0, 80) + topleft;
+               m_dependencies_listbox = 
+                       Environment->addListBox(rect, this, GUI_ID_DEPENDS_LISTBOX, true);
+       }
+       {
+               core::rect<s32> rect(0, 0, 200, 20);
+               rect += v2s32(0, 175) + topleft;
+               Environment->addStaticText(wgettext("is required by:"),
+                                       rect, false, false, this, -1);
+       }
+       {
+               core::rect<s32> rect(0, 0, 200, 85);
+               rect += v2s32(0, 195) + topleft;
+               m_rdependencies_listbox = 
+                       Environment->addListBox(rect,this, GUI_ID_RDEPENDS_LISTBOX,true);
+       }
+       {
+               core::rect<s32> 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<s32> rect(0, 0, 120, 30);
+               rect = rect + v2s32(330, 270) - topleft;
+               Environment->addButton(rect, this, GUI_ID_CANCEL,
+                       wgettext("Cancel"));
+       }
+       {
+               core::rect<s32> 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."
+                                               <<std::endl;
+                               // Returning true disables focus change
+                               return true;
+                       }
+               }
+               if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED){
+                       switch(event.GUIEvent.Caller->getID()){
+                       case GUI_ID_CANCEL: {
+                               quitMenu();
+                               return true;
+                       }
+                       case GUI_ID_SAVE: {
+                               for(std::set<std::string>::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<std::string, gui::IGUITreeViewNode*>::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<std::string, ModSpec> mods, 
+                                                                         gui::IGUITreeViewNode* node)
+{
+       for(std::map<std::string,ModSpec>::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<std::string>::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<std::string, std::string>::iterator, 
+                       std::multimap<std::string, std::string>::iterator > rdep = 
+               m_reverse_depends.equal_range(modname);
+       for(std::multimap<std::string,std::string>::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<std::string, ModSpec> mods,bool enable)
+{
+       for(std::map<std::string, ModSpec>::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<std::string,gui::IGUITreeViewNode*>::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<std::string>::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<std::string,gui::IGUITreeViewNode*>::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<std::multimap<std::string, std::string>::iterator, 
+                         std::multimap<std::string, std::string>::iterator > rdep = 
+               m_reverse_depends.equal_range(modname);
+       for(std::multimap<std::string,std::string>::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 (file)
index 0000000..2280c6d
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+Minetest-c55
+Copyright (C) 2012 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.
+*/
+
+#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<std::string, ModSpec> m_addontree;
+
+       // like m_addontree, but modpacks are expanded.
+       std::map<std::string, ModSpec> m_addonmods;
+
+       // list of game mods (flattened)
+       std::map<std::string, ModSpec> m_gamemods;
+
+       // list of world mods (flattened)
+       std::map<std::string, ModSpec> m_worldmods;
+       
+       // for each mod, the set of mods depending on it
+       std::multimap<std::string, std::string> 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<std::string> m_new_mod_names;
+
+       // maps modnames to nodes in m_treeview
+       std::map<std::string,gui::IGUITreeViewNode*> 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<std::string,ModSpec> mods, 
+                                          gui::IGUITreeViewNode* node);
+       void adjustSidebar();
+       void enableAllMods(std::map<std::string,ModSpec> 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
index ca0c1317c35d46102d2bcf4030737242d78835b8..77a5a85b87df810ac91c0873dfc37e8ba8085823 100644 (file)
@@ -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);
index 08e8e276f31f19a5169fc5620d9b12b75e7c5266..9e3c7d5d72f4d957ea3ba3414418cde73fb03026 100644 (file)
@@ -18,23 +18,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 */
 
 #include "mods.h"
-#include <queue>
-#include <fstream>
-#include <sstream>
-#include <map>
 #include "filesys.h"
 #include "strfnd.h"
 #include "log.h"
+#include "subgame.h"
+#include "settings.h"
 
-static void collectMods(const std::string &modspath,
-               std::queue<ModSpec> &mods_satisfied,
-               core::list<ModSpec> &mods_unsorted,
-               std::map<std::string, std::string> &mod_names,
-               std::string indentation)
+std::map<std::string, ModSpec> getModsInPath(std::string path)
 {
-       TRACESTREAM(<<indentation<<"collectMods(\""<<modspath<<"\")"<<std::endl);
-       TRACEDO(indentation += "  ");
-       std::vector<fs::DirListNode> dirlist = fs::GetDirListing(modspath);
+       std::map<std::string, ModSpec> result;
+       std::vector<fs::DirListNode> dirlist = fs::GetDirListing(path);
        for(u32 j=0; j<dirlist.size(); j++){
                if(!dirlist[j].dir)
                        continue;
@@ -43,119 +36,186 @@ static void collectMods(const std::string &modspath,
                // VCS directories like ".git" or ".svn"
                if(modname[0] == '.')
                        continue;
-               std::string modpath = modspath + DIR_DELIM + modname;
-               TRACESTREAM(<<indentation<<"collectMods: "<<modname<<" at \""<<modpath<<"\""<<std::endl);
+               std::string modpath = path + DIR_DELIM + modname;
+
                // Handle modpacks (defined by containing modpack.txt)
-               {
-                       std::ifstream is((modpath+DIR_DELIM+"modpack.txt").c_str(),
+               std::ifstream modpack_is((modpath+DIR_DELIM+"modpack.txt").c_str(),
                                        std::ios_base::binary);
-                       if(is.good()){
-                               // Is a modpack
-                               is.close(); // We don't actually need the file
-                               // Recurse into it
-                               collectMods(modpath, mods_satisfied, mods_unsorted, mod_names, indentation);
-                               continue;
-                       }
-               }
-               // Detect mod name conflicts
+               if(modpack_is.good()) //a modpack, recursively get the mods in it
                {
-                       std::map<std::string, std::string>::const_iterator i;
-                       i = mod_names.find(modname);
-                       if(i != mod_names.end()){
-                               std::string s;
-                               infostream<<indentation<<"WARNING: Mod name conflict detected: "
-                                               <<std::endl
-                                               <<"Already loaded: "<<i->second<<std::endl
-                                               <<"Will not load: "<<modpath<<std::endl;
-                               continue;
-                       }
+                       modpack_is.close(); // We don't actually need the file
+                       ModSpec spec(modname,modpath);
+                       spec.modpack_content = getModsInPath(modpath);
+                       result.insert(std::make_pair(modname,spec));
                }
-               std::set<std::string> depends;
-               std::ifstream is((modpath+DIR_DELIM+"depends.txt").c_str(),
+               else // not a modpack, add the modspec
+               {
+                       std::set<std::string> 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<ModSpec> getMods(core::list<std::string> &modspaths)
-               throw(ModError)
+std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods)
 {
-       std::queue<ModSpec> mods_satisfied;
-       core::list<ModSpec> mods_unsorted;
-       core::list<ModSpec> mods_sorted;
-       // name, path: For detecting name conflicts
-       std::map<std::string, std::string> mod_names;
-       for(core::list<std::string>::Iterator i = modspaths.begin();
-                       i != modspaths.end(); i++){
-               std::string modspath = *i;
-               collectMods(modspath, mods_satisfied, mods_unsorted, mod_names, "");
+       std::map<std::string, ModSpec> result;
+       for(std::map<std::string,ModSpec>::iterator it = mods.begin();
+               it != mods.end(); ++it)
+       {
+               ModSpec mod = (*it).second;
+               if(!mod.modpack_content.empty()) //is a modpack
+               {
+                       std::map<std::string, ModSpec> 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));
+               }
        }
-       // Sort by depencencies
-       while(!mods_satisfied.empty()){
-               ModSpec mod = mods_satisfied.front();
-               mods_satisfied.pop();
-               mods_sorted.push_back(mod);
-               for(core::list<ModSpec>::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);
+       return result;
+}
+
+std::vector<ModSpec> flattenMods(std::map<std::string, ModSpec> mods)
+{
+       std::vector<ModSpec> result;
+       for(std::map<std::string,ModSpec>::iterator it = mods.begin();
+               it != mods.end(); ++it)
+       {
+               ModSpec mod = (*it).second;
+               if(!mod.modpack_content.empty()) //is a modpack
+               {
+                       std::vector<ModSpec> 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);
                }
        }
-       std::ostringstream errs(std::ios::binary);
-       // Check unsatisfied dependencies
-       for(core::list<ModSpec>::Iterator i = mods_unsorted.begin();
-                       i != mods_unsorted.end(); i++){
-               ModSpec &mod = *i;
-               if(mod.unsatisfied_depends.empty())
-                       continue;
-               errs<<"mod \""<<mod.name
-                               <<"\" has unsatisfied dependencies:";
-               for(std::set<std::string>::iterator
-                               i = mod.unsatisfied_depends.begin();
-                               i != mod.unsatisfied_depends.end(); i++){
-                       errs<<" \""<<(*i)<<"\"";
+       return result;
+}
+
+std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
+                                                               std::set<std::string> exclude_mod_names)
+{
+       std::vector<ModSpec> result;
+       for(std::vector<ModSpec>::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<std::string> exclude_mods)
+{
+       addMods(filterMods(flattenMods(getModsInPath(path)),exclude_mods));
+}
+
+
+void ModConfiguration::addMods(std::vector<ModSpec> 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<ModSpec> satisfied_mods;
+       for(std::vector<ModSpec>::iterator it = m_sorted_mods.begin();
+               it != m_sorted_mods.end(); ++it)
+       {
+               ModSpec mod = *it;
+               for(std::vector<ModSpec>::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);
                }
-               errs<<"."<<std::endl;
-               mods_sorted.push_back(mod);
        }
-       // If an error occurred, throw an exception
-       if(errs.str().size() != 0){
-               throw ModError(errs.str());
+
+       // split new mods into satisfied and unsatisfied
+       for(std::vector<ModSpec>::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);
        }
-       // Print out some debug info
-       TRACESTREAM(<<"Detected mods in load order:"<<std::endl);
-       for(core::list<ModSpec>::Iterator i = mods_sorted.begin();
-                       i != mods_sorted.end(); i++){
-               ModSpec &mod = *i;
-               TRACESTREAM(<<"name=\""<<mod.name<<"\" path=\""<<mod.path<<"\"");
-               TRACESTREAM(<<" depends=[");
-               for(std::set<std::string>::iterator i = mod.depends.begin();
-                               i != mod.depends.end(); i++)
-                       TRACESTREAM(<<" "<<(*i));
-               TRACESTREAM(<<" ] unsatisfied_depends=[");
-               for(std::set<std::string>::iterator i = mod.unsatisfied_depends.begin();
-                               i != mod.unsatisfied_depends.end(); i++)
-                       TRACESTREAM(<<" "<<(*i));
-               TRACESTREAM(<<" ]"<<std::endl);
+
+       // 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<ModSpec>::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;
+               }       
        }
-       return mods_sorted;
 }
 
+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_<modname> = ... line.
+       std::string worldmt = worldpath+DIR_DELIM+"world.mt";
+       Settings worldmt_settings;
+       worldmt_settings.readConfigFile(worldmt.c_str());
+       std::vector<std::string> names = worldmt_settings.getNames();
+       std::set<std::string> exclude_mod_names;
+       for(std::vector<std::string>::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<std::string>::const_iterator i = gamespec.addon_mods_paths.begin();
+               i != gamespec.addon_mods_paths.end(); ++i)
+               addModsInPathFiltered((*i),exclude_mod_names);
+}
index d55dd6c92ea4b19cc3b208319cab1d82fdb07dd1..37e2cd2db5c62a541826c5763e1f1446ab925e64 100644 (file)
@@ -22,8 +22,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "irrlichttypes.h"
 #include <irrList.h>
+#include <list>
 #include <set>
+#include <vector>
 #include <string>
+#include <map>
 #include <exception>
 
 class ModError : public std::exception
@@ -47,21 +50,93 @@ struct ModSpec
 {
        std::string name;
        std::string path;
+       //if normal mod:
        std::set<std::string> depends;
        std::set<std::string> unsatisfied_depends;
-
-       ModSpec(const std::string &name_="", const std::string path_="",
-                       const std::set<std::string> &depends_=std::set<std::string>()):
+       // if modpack:
+       std::map<std::string,ModSpec> modpack_content;
+       ModSpec(const std::string name_="", const std::string path_="",
+                       const std::set<std::string> depends_=std::set<std::string>()):
                name(name_),
                path(path_),
                depends(depends_),
-               unsatisfied_depends(depends_)
+               unsatisfied_depends(depends_),
+               modpack_content()       
        {}
 };
 
-// Get a dependency-sorted list of ModSpecs
-core::list<ModSpec> getMods(core::list<std::string> &modspaths)
-               throw(ModError);
+
+std::map<std::string,ModSpec> getModsInPath(std::string path);
+
+// expands modpack contents, but does not replace them.
+std::map<std::string, ModSpec> flattenModTree(std::map<std::string, ModSpec> mods);
+
+// replaces modpack Modspecs with their content
+std::vector<ModSpec> flattenMods(std::map<std::string,ModSpec> mods);
+
+// removes Mods mentioned in exclude_mod_names
+std::vector<ModSpec> filterMods(std::vector<ModSpec> mods,
+                                                               std::set<std::string> 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<std::string> exclude_mods);
+
+       // adds all mods in the set.
+       void addMods(std::vector<ModSpec> mods);
+
+       // checks if all dependencies are fullfilled.
+       bool isConsistent()
+       {
+               return m_unsatisfied_mods.empty();
+       }
+
+       std::vector<ModSpec> getMods()
+       {
+               return m_sorted_mods;
+       }
+
+       std::list<ModSpec> 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<ModSpec> 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<ModSpec> m_sorted_mods;
+
+};
+
 
 #endif
 
index 0a82883240789d3790de5774f7dc69a9d07c6356..853d0a486bd26bf58c8eff693c7a54dc869db229 100644 (file)
@@ -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<std::string>::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<std::string>::Iterator i = m_modspaths.begin();
-                       i != m_modspaths.end(); i++){
-               std::string modspath = *i;
-               infostream<<"- mods:   "<<modspath<<std::endl;
-       }
-       
-       // Path to builtin.lua
-       std::string builtinpath = getBuiltinLuaPath() + DIR_DELIM + "builtin.lua";
-
        // Create world if it doesn't exist
        if(!initializeWorld(m_path_world, m_gamespec.id))
                throw ServerError("Failed to initialize world");
 
+       ModConfiguration modconf(m_path_world);
+       m_mods = modconf.getMods();
+       // complain about mods with unsatisfied dependencies
+       if(!modconf.isConsistent())     
+       {
+               errorstream << "The following mods have unsatisfied dependencies: ";
+               std::list<ModSpec> modlist = modconf.getUnsatisfiedMods();
+               for(std::list<ModSpec>::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<std::string> names = worldmt_settings.getNames();
+       std::set<std::string> exclude_mod_names;
+       std::set<std::string> load_mod_names;
+       for(std::vector<std::string>::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<ModSpec>::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<std::string>::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"<<std::endl;
        m_lua = script_init();
        assert(m_lua);
@@ -1021,18 +1053,16 @@ Server::Server(
                                <<builtinpath<<std::endl;
                throw ModError("Failed to load and run "+builtinpath);
        }
-       // Find mods in mod search paths
-       m_mods = getMods(m_modspaths);
        // Print 'em
        infostream<<"Server: Loading mods: ";
-       for(core::list<ModSpec>::Iterator i = m_mods.begin();
+       for(std::vector<ModSpec>::iterator i = m_mods.begin();
                        i != m_mods.end(); i++){
                const ModSpec &mod = *i;
                infostream<<mod.name<<" ";
        }
        infostream<<std::endl;
        // Load and run "mod" scripts
-       for(core::list<ModSpec>::Iterator i = m_mods.begin();
+       for(std::vector<ModSpec>::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<std::string> paths;
-       for(core::list<ModSpec>::Iterator i = m_mods.begin();
+       for(std::vector<ModSpec>::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<ModSpec>::Iterator i = m_mods.begin();
+       for(std::vector<ModSpec>::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<std::string> &modlist)
 {
-       for(core::list<ModSpec>::Iterator i = m_mods.begin(); i != m_mods.end(); i++)
+       for(std::vector<ModSpec>::iterator i = m_mods.begin(); i != m_mods.end(); i++)
        {
                modlist.push_back((*i).name);
        }
index 43beb167ae0a5b12c36db1b10763ec36443dfbc6..26e47d36c9195f35930cb8f5565ed458ed26db22 100644 (file)
@@ -756,7 +756,7 @@ private:
        EventManager *m_event;
        
        // Mods
-       core::list<ModSpec> m_mods;
+       std::vector<ModSpec> m_mods;
        
        /*
                Threads
index 1ab6fcc6b550740181ed3eb888a5770144f14361..1a29ef00a398fd4460e90d8c2b2b6d5da06978d1 100644 (file)
@@ -71,6 +71,26 @@ public:
                        os<<name<<" = "<<value<<"\n";
                }
        }
+  
+       // return all keys used 
+       std::vector<std::string> getNames(){
+               std::vector<std::string> names;
+               for(core::map<std::string, std::string>::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)
        {
index c36189933237acd8bd8c983206bce5f6607d1d5a..2f534037312480a0d87db1284b23cfe6aeed195b 100644 (file)
@@ -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<std::string> 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";
index bffa86e285813da61adc76443bb55da2e3800ce9..dd725caf7b385211aef2ca4df546d1d6cd40cc78 100644 (file)
@@ -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<std::string> mods_paths;
+       std::string path; // path to game
+       std::string gamemods_path; //path to mods of the game
+       std::set<std::string> 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<std::string> &mods_paths_=std::set<std::string>(),
+                       const std::string &path_="",    
+                       const std::string &gamemods_path_="",
+                       const std::set<std::string> &addon_mods_paths_=std::set<std::string>(),
                        const std::string &name_=""):
                id(id_),
                path(path_),
-               mods_paths(mods_paths_),
+               gamemods_path(gamemods_path_),          
+               addon_mods_paths(addon_mods_paths_),
                name(name_)
        {}