summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJürgen Doser <jurgen.doser@gmail.com>2012-12-08 18:10:54 +0100
committerPerttu Ahola <celeron55@gmail.com>2013-01-21 17:31:50 +0200
commit6af8a34d91582ce15a0341e75d3e55d847a5a47a (patch)
tree58651158dbc0c897dd110ffade114d250be5e597
parent6f93c01af942d75db105eac6afcd7d477967ccb6 (diff)
downloadminetest-6af8a34d91582ce15a0341e75d3e55d847a5a47a.tar.gz
minetest-6af8a34d91582ce15a0341e75d3e55d847a5a47a.tar.bz2
minetest-6af8a34d91582ce15a0341e75d3e55d847a5a47a.zip
Basic support for configuring which mods to load for each world
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
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/guiConfigureWorld.cpp668
-rw-r--r--src/guiConfigureWorld.h110
-rw-r--r--src/guiMainMenu.cpp22
-rw-r--r--src/mods.cpp276
-rw-r--r--src/mods.h89
-rw-r--r--src/server.cpp80
-rw-r--r--src/server.h2
-rw-r--r--src/settings.h20
-rw-r--r--src/subgame.cpp6
-rw-r--r--src/subgame.h13
11 files changed, 1133 insertions, 154 deletions
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 <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
index 000000000..2280c6dbe
--- /dev/null
+++ b/src/guiConfigureWorld.h
@@ -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
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 <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);
+}
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 <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
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<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);
}
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<ModSpec> m_mods;
+ std::vector<ModSpec> 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<<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)
{
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<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";
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<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_)
{}