forked from Mirrorlandia_minetest/minetest
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
This commit is contained in:
parent
6f93c01af9
commit
6af8a34d91
@ -275,6 +275,7 @@ set(minetest_SRCS
|
||||
guiDeathScreen.cpp
|
||||
guiChatConsole.cpp
|
||||
guiCreateWorld.cpp
|
||||
guiConfigureWorld.cpp
|
||||
guiConfirmMenu.cpp
|
||||
client.cpp
|
||||
filecache.cpp
|
||||
|
668
src/guiConfigureWorld.cpp
Normal file
668
src/guiConfigureWorld.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
110
src/guiConfigureWorld.h
Normal file
110
src/guiConfigureWorld.h
Normal file
@ -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
|
@ -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);
|
||||
|
282
src/mods.cpp
282
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, "");
|
||||
}
|
||||
// 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);
|
||||
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));
|
||||
}
|
||||
}
|
||||
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> 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);
|
||||
}
|
||||
errs<<"."<<std::endl;
|
||||
mods_sorted.push_back(mod);
|
||||
}
|
||||
// If an error occurred, throw an exception
|
||||
if(errs.str().size() != 0){
|
||||
throw ModError(errs.str());
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
return mods_sorted;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
89
src/mods.h
89
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
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -756,7 +756,7 @@ private:
|
||||
EventManager *m_event;
|
||||
|
||||
// Mods
|
||||
core::list<ModSpec> m_mods;
|
||||
std::vector<ModSpec> m_mods;
|
||||
|
||||
/*
|
||||
Threads
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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";
|
||||
|
@ -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_)
|
||||
{}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user