Applied patch #3297

This commit is contained in:
Boldizsár Lipka 2012-09-25 17:42:18 +00:00
parent 1738722c2e
commit bcaa7f4f2c
31 changed files with 3411 additions and 18 deletions

View File

@ -14,7 +14,16 @@ The release team should empty this file after each release.
CHANGES
=======
[section="A Change"]
[section="Multiplayer modifications"]
Starting with this release, there's a new type of add-on, MP Modification, which allows alterations to the game play independently from eras and scenarios. Additionally, you can have any amount of modifications enabled at a time. You can access installed modifications with the "Modifications" button on the game creation screen. See [wiki]ModificationWML[/wiki] for details.
[/section]
[section="Multiplayer option system"]
Another improvement is the new option system. UMC authors now can have their add-ons display some configuration options on the game creation screen. The settings are translated into WML variables inside the add-on. The options for activated modifications, era or scenario are accessible through the "Options" button on the game creation screen. See [wiki]OptionWML[/wiki] for details.
[/section]
[section="Multiplayer dependency system"]
With a lot of new possibilities for conflicts between add-ons, there's now a dependency system for eras, scenarios and modifications, not to be confused with the already existing dependency management for whole add-ons. See the appopriate sections of [wiki]EraWML[/wiki], [wiki]ScenarioWML[/wiki] and [wiki]ModificationWML[/wiki] for details.
[/section]
[section="Another Change"]

View File

@ -234,6 +234,8 @@ Version 1.11.0:
* Changes to the time of day schedules of Fallenstar Lake and Silverhead
Crossing
* Random leader is default selection when picking faction
* Added support for modification tags
* Added support for dependencies between eras, scenarios and modifications
* Music and sound effects:
* Replaced some of the wolf hit sounds with lower-pitched ones
* Terrain:

View File

@ -0,0 +1,220 @@
#textdomain wesnoth-lib
###
### Select the modifications to be active during the game
###
[window]
id = "mp_create_game_choose_mods"
description = "Dialog for choosing modifications for MP games."
[resolution]
definition = "default"
automatic_placement = "true"
vertical_placement = "center"
horizontal_placement = "center"
[linked_group]
id = "checkbox"
fixed_width = "true"
[/linked_group]
[linked_group]
id = "name"
fixed_width = "true"
[/linked_group]
[linked_group]
id = "description"
fixed_width = "true"
[/linked_group]
[tooltip]
id = "tooltip_large"
[/tooltip]
[helptip]
id = "tooltip_large"
[/helptip]
[grid]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "title"
label = _ "Choose Modifications"
[/label]
[/column]
[/row]
[row]
grow_factor = 0
[column]
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "default"
id = "message"
label = _ "Enable the modifications you want to be active during the game."
[/label]
[/column]
[/row]
[row]
grow_factor = 1
[column]
grow_factor = 1
horizontal_grow = "true"
vertical_grow = "true"
border = "all"
border_size = 5
[listbox]
id = "mod_list"
definition = "default"
[list_definition]
[row]
[column]
vertical_grow = "true"
horizontal_grow = "true"
[toggle_panel]
definition = "default"
[grid]
[row]
[column]
grow_factor = 0
horizontal_alignment = "left"
border = "all"
border_size = 5
[toggle_button]
id = "checkbox"
definition = "default"
linked_group = "checkbox"
[/toggle_button]
[/column]
[column]
grow_factor = 1
horizontal_grow = "true"
border = "all"
border_size = 5
[label]
id = "name"
definition = "default_large"
linked_group = "name"
[/label]
[/column]
[/row]
[row]
[column]
grow_factor = 0
horizontal_alignment = "left"
border = "all"
border_size = 5
[spacer]
[/spacer]
[/column]
[column]
grow_factor = 1
horizontal_alignment = "left"
border = "all"
border_size = 5
[label]
id = "description"
definition = "default"
linked_group = "description"
[/label]
[/column]
[/row]
[/grid]
[/toggle_panel]
[/column]
[/row]
[/list_definition]
[/listbox]
[/column]
[/row]
[row]
grow_factor = 0
[column]
border = "all"
border_size = 5
horizontal_alignment = "right"
[grid]
[row]
[column]
border = "all"
border_size = 5
horizontal_alignment = "right"
[button]
id = "ok"
definition = "default"
label = _ "OK"
[/button]
[/column]
[column]
border = "all"
border_size = 5
horizontal_alignment = "right"
[button]
id = "cancel"
definition = "default"
label = _ "Cancel"
[/button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
[/resolution]
[/window]

View File

@ -0,0 +1,161 @@
#textdomain wesnoth-lib
###
### Prompts the user to confirm some changes required to satisfy
### dependencies. Currently used for enabling/disabling modifications.
###
[window]
id = "mp_depcheck_confirm_change"
description = "Enable/disable modifications"
[resolution]
definition = "default"
automatic_placement = "true"
vertical_placement = "center"
horizontal_placement = "center"
[tooltip]
id = "tooltip_large"
[/tooltip]
[helptip]
id = "tooltip_large"
[/helptip]
[grid]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "title"
label = _ "Confirmation requested"
[/label]
[/column]
[/row]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "default"
label = "text is set by C++"
id = "message"
[/label]
[/column]
[/row]
[row]
grow_factor = 1
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "left"
[scroll_label]
id = "itemlist"
label = "text set by c++"
definition = "default"
[/scroll_label]
[/column]
[/row]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
label = _ "Would you like to apply the changes?"
definition = "default"
[/label]
[/column]
[/row]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "right"
[grid]
[row]
grow_factor = 0
[column]
grow_factor = 0
border = "all"
border_size = 5
horizontal_alignment = "center"
[button]
id = "ok"
label = _ "Yes"
[/button]
[/column]
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "right"
[button]
id = "cancel"
label = _ "No"
[/button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
[/resolution]
[/window]

View File

@ -0,0 +1,164 @@
#textdomain wesnoth-lib
###
### Proposes a list of compaitble components if the currently selected
### one is incompatible. Currently used for scenarios and eras.
###
[window]
id = "mp_depcheck_select_new"
description = "Select new era or scenario"
[resolution]
definition = "default"
automatic_placement = "true"
horizontal_placement = "center"
vertical_placement = "center"
[tooltip]
id = "tooltip_large"
[/tooltip]
[helptip]
id = "tooltip_large"
[/helptip]
[grid]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "title"
label = _ "User interaction required"
[/label]
[/column]
[/row]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "default"
label = _ "User interaction required"
id = "message"
[/label]
[/column]
[/row]
[row]
grow_factor = 1
[column]
grow_factor = 1
horizontal_grow = "true"
vertical_grow = "true"
border = "all"
border_size = 5
[listbox]
definition = "default"
id = "itemlist"
[list_definition]
[row]
[column]
grow_factor = 1
horizontal_grow = "true"
[toggle_button]
definition = "listbox_text"
return_value_id = "ok"
[/toggle_button]
[/column]
[/row]
[/list_definition]
[/listbox]
[/column]
[/row]
[row]
grow_factor = 0
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "right"
[grid]
[row]
grow_factor = 0
[column]
grow_factor = 0
border = "all"
border_size = 5
horizontal_alignment = "center"
[button]
id = "ok"
label = _ "OK"
[/button]
[/column]
[column]
grow_factor = 1
border = "all"
border_size = 5
horizontal_alignment = "right"
[button]
id = "cancel"
label = _ "Cancel"
[/button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
[/resolution]
[/window]

View File

@ -44,6 +44,11 @@ Version 1.11.0+svn:
in the Load Game dialog.
* Fix OOS when dismissing a recall in a multiplayer campaign (bug #19924).
* Multiplayer
* New add-on type: modifications
* GUI2 configuration options for eras, scenarios and modifications
* Dependency system for eras, scenarios and modifications
Version 1.11.0:
* Add-ons client:

View File

@ -721,7 +721,10 @@ set(wesnoth-main_SRC
gui/dialogs/mp_cmd_wrapper.cpp
gui/dialogs/mp_connect.cpp
gui/dialogs/mp_create_game.cpp
gui/dialogs/mp_create_game_choose_mods.cpp
gui/dialogs/mp_create_game_set_password.cpp
gui/dialogs/mp_depcheck_confirm_change.cpp
gui/dialogs/mp_depcheck_select_new.cpp
gui/dialogs/mp_host_game_prompt.cpp
gui/dialogs/mp_login.cpp
gui/dialogs/mp_method_selection.cpp
@ -743,7 +746,9 @@ set(wesnoth-main_SRC
menu_events.cpp
mouse_events.cpp
mouse_handler_base.cpp
mp_depcheck.cpp
mp_game_settings.cpp
mp_options.cpp
multiplayer.cpp
multiplayer_connect.cpp
multiplayer_create.cpp

View File

@ -62,7 +62,7 @@ if env["PLATFORM"] != "win32":
if env['default_prefs_file']:
client_env.Append(CPPDEFINES = "DEFAULT_PREFS_PATH='\"$default_prefs_file\"'")
game_config_env['default_prefs_file'] = env['default_prefs_file']
game_config_env.Append(CPPDEFINES = "DEFAULT_PREFS_PATH='\"$default_prefs_file\"'")
if not os.path.isabs(env['default_prefs_file']):
@ -363,7 +363,10 @@ wesnoth_sources = Split("""
gui/dialogs/mp_change_control.cpp
gui/dialogs/mp_connect.cpp
gui/dialogs/mp_create_game.cpp
gui/dialogs/mp_create_game_choose_mods.cpp
gui/dialogs/mp_create_game_set_password.cpp
gui/dialogs/mp_depcheck_confirm_change.cpp
gui/dialogs/mp_depcheck_select_new.cpp
gui/dialogs/mp_host_game_prompt.cpp
gui/dialogs/mp_login.cpp
gui/dialogs/mp_method_selection.cpp
@ -402,13 +405,13 @@ wesnoth_sources = Split("""
gui/widgets/scrollbar_container.cpp
gui/widgets/scrollbar_panel.cpp
gui/widgets/settings.cpp
gui/widgets/slider.cpp
gui/widgets/slider.cpp
gui/widgets/spacer.cpp
gui/widgets/stacked_widget.cpp
gui/widgets/text.cpp
gui/widgets/text_box.cpp
gui/widgets/toggle_button.cpp
gui/widgets/toggle_panel.cpp
gui/widgets/toggle_button.cpp
gui/widgets/toggle_panel.cpp
gui/widgets/tree_view.cpp
gui/widgets/tree_view_node.cpp
gui/widgets/vertical_scrollbar.cpp
@ -423,7 +426,9 @@ wesnoth_sources = Split("""
menu_events.cpp
mouse_events.cpp
mouse_handler_base.cpp
mp_depcheck.cpp
mp_game_settings.cpp
mp_options.cpp
multiplayer.cpp
multiplayer_connect.cpp
multiplayer_create.cpp

View File

@ -53,6 +53,12 @@ std::set<std::string> ignores;
bool friends_initialized = false;
bool ignores_initialized = false;
std::vector<std::string> mp_modifications;
bool modifications_initialized = false;
config option_values;
bool options_initialized = false;
bool authenticated = false;
const char WRAP_CHAR = '@';
@ -77,10 +83,17 @@ std::string parse_wrapped_credentials_field(const std::string& raw)
return raw.substr(1, raw.length() - 2);
}
void initialize_modifications()
{
mp_modifications = utils::split(preferences::get("modifications"), ',');
modifications_initialized = true;
}
} // anon namespace
namespace preferences {
manager::manager() :
base()
{
@ -580,6 +593,32 @@ void set_turns(int value)
preferences::set("mp_turns", value);
}
const config& options()
{
if (options_initialized) {
return option_values;
}
if (!preferences::get_child("options")) {
// It may be an invalid config, which would cause problems in
// multiplayer_create, so let's replace it with an empty but valid
// config
option_values = config();
} else {
option_values = preferences::get_child("options");
}
options_initialized = true;
return option_values;
}
void set_options(const config& values)
{
preferences::set_child("options", values);
options_initialized = false;
}
bool skip_mp_replay()
{
return preferences::get("skip_mp_replay", false);
@ -694,6 +733,20 @@ void set_map(int value)
preferences::set("mp_map", value);
}
const std::vector<std::string>& modifications()
{
if (!modifications_initialized)
initialize_modifications();
return mp_modifications;
}
void set_modifications(const std::vector<std::string>& value)
{
preferences::set("modifications", utils::join(value, ","));
modifications_initialized = false;
}
bool show_ai_moves()
{
return preferences::get("show_ai_moves", true);

View File

@ -23,6 +23,7 @@ class unit_map;
#include "preferences.hpp"
#include <set>
#include <vector>
namespace preferences {
@ -143,6 +144,9 @@ namespace preferences {
int turns();
void set_turns(int value);
const config& options();
void set_options(const config& values);
bool skip_mp_replay();
void set_skip_mp_replay(bool value);
@ -172,6 +176,9 @@ namespace preferences {
int map();
void set_map(int value);
const std::vector<std::string>& modifications();
void set_modifications(const std::vector<std::string>& value);
bool show_ai_moves();
void set_show_ai_moves(bool value);

View File

@ -0,0 +1,114 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/mp_create_game_choose_mods.hpp"
#include "game_preferences.hpp"
#include "gui/dialogs/field.hpp"
#include "gui/dialogs/helper.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/toggle_button.hpp"
#include "../../settings.hpp"
#include "gettext.hpp"
namespace gui2 {
/*WIKI
* @page = GUIWindowDefinitionWML
* @order = 2_mp_create_game_choose_mods
*
* == Create Game: Choose Modifications ==
*
* The dialog for selecting modifications.
*
* @begin{table}{dialog_widgets}
*
* mod_list & & listbox & m &
* displays the list of the available modifications $
*
* -checkbox & & toggle_button & o &
* enable/disable a modification $
*
* -name & & label & o &
* displays the modification's name $
*
* -description & & label & o &
* displays the modification's description $
*
* ok & & button & m &
* closes the dialog, applies changes $
*
* cancel & & button & m &
* closes the dialog, discards changes $
*
* @end{table}
*/
REGISTER_DIALOG(mp_create_game_choose_mods)
tmp_create_game_choose_mods::tmp_create_game_choose_mods
(const config& game_cfg,
std::vector<std::string>& result)
: result_(result)
, game_cfg_(game_cfg)
{
}
void tmp_create_game_choose_mods::pre_show(CVideo &/*video*/, twindow &window)
{
mod_list_ = find_widget<tlistbox>(&window, "mod_list", false, true);
std::vector<string_map>::iterator mod_itor;
BOOST_FOREACH (const config& mod, game_cfg_.child_range("modification")) {
string_map column;
std::map<std::string, string_map> item;
column["label"] = mod["name"];
item.insert(std::make_pair("name", column));
column["label"] = mod["description"];
item.insert(std::make_pair("description", column));
mod_list_->add_row(item);
tgrid *grid = mod_list_->get_row_grid(mod_list_->get_item_count()-1);
ttoggle_button &checkbox =
find_widget<ttoggle_button>(grid, "checkbox", false);
checkbox.set_value(std::find( result_.begin(), result_.end(),
mod["id"]) != result_.end());
}
}
void tmp_create_game_choose_mods::post_show(twindow& /*window*/)
{
if (get_retval() == twindow::OK) {
result_.clear();
for(unsigned int i = 0; i < mod_list_->get_item_count(); i++) {
const tgrid *grid = mod_list_->get_row_grid(i);
const ttoggle_button &checkbox =
find_widget<const ttoggle_button>(grid, "checkbox", false);
if (checkbox.get_value()) {
result_.push_back(game_cfg_.child("modification", i)["id"]);
}
}
}
}
} // namespace gui2

View File

@ -0,0 +1,67 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#ifndef GUI_DIALOGS_MP_CREATE_GAME_CHOOSE_MODS_HPP_INCLUDED
#define GUI_DIALOGS_MP_CREATE_GAME_CHOOSE_MODS_HPP_INCLUDED
#include "gui/dialogs/dialog.hpp"
#include "gui/widgets/listbox.hpp"
#include "gui/widgets/button.hpp"
#include <utility>
#include <vector>
#include <string>
class config;
namespace gui2 {
class tmp_create_game_choose_mods : public tdialog
{
public:
/**
* Constructor.
*
* @param game_cfg the config which contains the information for the
* modifications
* @param result [in] the list of the currently activated modifications
* @param result [out] the list of all activated modifications
*/
tmp_create_game_choose_mods(const config& game_cfg,
std::vector<std::string>& result);
private:
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
/** Inherited from tdialog */
void pre_show(CVideo &video, twindow &window);
/** Inherited from tdialog */
void post_show(twindow &window);
/** a reference to the variable the result should be written into */
std::vector<std::string>& result_;
/** a reference to the config which contains the info about modifications*/
const config& game_cfg_;
/** a pointer to the listbox which displays the items */
gui2::tlistbox *mod_list_;
};
} // namespace gui2
#endif

View File

@ -0,0 +1,77 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/mp_depcheck_confirm_change.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "formula_string_utils.hpp"
#include "gettext.hpp"
namespace gui2
{
/*WIKI
* @page = GUIWindowDefinitionWML
* @order = 2_mp_depcheck_confirm_change
*
* == MP Dependency Check: Confirm Change ==
*
* Asks the user to confirm a change required to proceed. Currently used
* for enabling/disabling modifications
*
* @begin{table}{dialog_widgets}
*
* message & & label & m &
* displays the details of the required changes $
*
* itemlist & & scroll_label & m &
* displays the list of affected items $
*
* cancel & & button & m &
* refuse to apply changes $
*
* ok & & button & m &
* agree to apply changes $
*
* @end{table}
*/
REGISTER_DIALOG(mp_depcheck_confirm_change)
tmp_depcheck_confirm_change::tmp_depcheck_confirm_change
(bool action,
const std::vector<std::string>& mods,
const std::string& requester)
{
utils::string_map symbols;
symbols["requester"] = requester;
std::string message;
if (action) {
message = vgettext("$requester requires the following modifications to be enabled:", symbols);
} else {
message = vgettext("$requester requires the following modifications to be disabled:", symbols);
}
std::string list = "\t";
list += utils::join(mods, "\n\t");
register_label("message", false, message);
register_label("itemlist", false, list);
}
}

View File

@ -0,0 +1,51 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#ifndef GUI_DIALOGS_MP_DEPCHECK_CONFIRM_CHANGE_HPP_INCLUDED
#define GUI_DIALOGS_MP_DEPCHECK_CONFIRM_CHANGE_HPP_INCLUDED
#include "gui/dialogs/dialog.hpp"
#include <vector>
#include <string>
namespace gui2
{
class tmp_depcheck_confirm_change
: public tdialog
{
public:
/**
* Constructor.
*
* @param action true if the listed modifications are to be enabled,
* false if they're to be disabled
* @param mods the names of the affected modifications
* @param requester the name of the component which requests the change
*
*/
tmp_depcheck_confirm_change(bool action,
const std::vector<std::string>& mods,
const std::string& requester );
protected:
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
};
} //namespace gui2
#endif

View File

@ -0,0 +1,110 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/mp_depcheck_select_new.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "gui/widgets/listbox.hpp"
#include "gettext.hpp"
namespace gui2
{
/*WIKI
* @page = GUIWindowDefinitionWML
* @order = 2_mp_depcheck_select_new
*
* == MP Dependency Check: Select New ==
*
* Offers a list of compatible items if a currently selected one is
* incompatible. Currently used for switching era or map.
*
* @begin{table}{dialog_widgets}
*
* message & & label & m &
* displays the details of the required changes $
*
* itemlist & & listbox & m &
* displays the available items to choose from $
*
* cancel & & button & m &
* refuse to apply any changes $
*
* ok & & button & m &
* select the chosen item $
*
* @end{table}
*
*/
REGISTER_DIALOG(mp_depcheck_select_new)
tmp_depcheck_select_new::tmp_depcheck_select_new
( mp::depcheck::component_type name,
const std::vector<std::string>& items )
: items_(items)
, result_(-1)
{
std::string message;
switch (name)
{
case mp::depcheck::SCENARIO:
message = _( "The currently chosen scenario " \
"is not compatible with your setup." \
"\nPlease select a compatible one.");
break;
case mp::depcheck::ERA:
message = _( "The currently chosen era " \
"is not compatible with your setup." \
"\nPlease select a compatible one.");
break;
case mp::depcheck::MODIFICATION:
//currently this can't happen, but be prepared for anything...
message = _( "The currently chosen modification " \
"is not compatible with your setup." \
"\nPlease select a compatible one.");
}
register_label("message", false, message);
}
void tmp_depcheck_select_new::pre_show(CVideo& /*video*/, twindow& window)
{
tlistbox& listbox = find_widget<tlistbox>(&window, "itemlist", false);
BOOST_FOREACH(const std::string& item, items_) {
string_map current;
current.insert(std::make_pair("label", item));
listbox.add_row(current);
}
listbox.select_row(0);
}
void tmp_depcheck_select_new::post_show(twindow& window)
{
if (get_retval() == twindow::OK) {
tlistbox& listbox = find_widget<tlistbox>(&window, "itemlist", false);
result_ = listbox.get_selected_row();
}
}
}

View File

@ -0,0 +1,66 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#ifndef GUI_DIALOGS_MP_DEPCHECK_SELECT_NEW_HPP_INCLUDED
#define GUI_DIALOGS_MP_DEPCHECK_SELECT_NEW_HPP_INCLUDED
#include "gui/dialogs/dialog.hpp"
#include "mp_depcheck.hpp"
#include <vector>
namespace gui2
{
class tmp_depcheck_select_new
: public tdialog
{
public:
/**
* Constructor.
*
* @param name the type of which we want to select a new item
* @param options the names of the components which can be choosed
*/
tmp_depcheck_select_new(mp::depcheck::component_type name,
const std::vector<std::string>& options);
/**
* Returns the selected item.
*
* @return the index of the selected item, or -1 if none was selected
* (the dialog was closed with the cancel button)
*/
int result() const { return result_; }
protected:
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
/** Inherited from tdialog */
virtual void pre_show(CVideo& video, twindow& window);
/** Inherited from tdialog */
virtual void post_show(twindow& window);
private:
/** the options available */
std::vector<std::string> items_;
/** the index of the selected item */
int result_;
};
}
#endif

719
src/mp_depcheck.cpp Normal file
View File

@ -0,0 +1,719 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#include "mp_depcheck.hpp"
#include <algorithm>
#include <map>
#include "formula_string_utils.hpp"
#include "gettext.hpp"
#include "log.hpp"
#include "gui/dialogs/mp_depcheck_confirm_change.hpp"
#include "gui/dialogs/mp_depcheck_select_new.hpp"
#include "gui/dialogs/message.hpp"
static lg::log_domain log_mp_create_depcheck("mp/create/depcheck");
#define DBG_MP LOG_STREAM(debug, log_mp_create_depcheck)
namespace {
//helper function
void copy_keys(config& out,
const config& in,
const std::string& type,
bool copy_force_key=false)
{
if (in.has_attribute("allow_" + type)) {
out["allow_" + type] = in["allow_" + type];
} else if (in.has_attribute("disallow_" + type)) {
out["disallow_" + type] = in["disallow_" + type];
}
if (in.has_attribute("ignore_incompatible_" + type)) {
out["ignore_incompatible_" + type] = in["ignore_incompatible_" + type];
}
if (copy_force_key) {
if (in.has_attribute("force_" + type)) {
out["force_" + type] = in["force_" + type];
}
}
}
//helper function
inline bool contains(const std::vector<std::string>& container,
const std::string& value)
{
return std::find(container.begin(), container.end(), value)
!= container.end();
}
} //anonymous namespace
namespace mp
{
namespace depcheck
{
manager::manager(const config& gamecfg, CVideo& video)
: video_(video)
, depinfo_()
, era_()
, scenario_()
, mods_()
, prev_era_()
, prev_scenario_()
, prev_mods_()
{
DBG_MP << "Initializing the dependency manager" << std::endl;
BOOST_FOREACH (const config& cfg, gamecfg.child_range("modification")) {
config info;
info["id"] = cfg["id"];
info["name"] = cfg["name"];
copy_keys(info, cfg, "scenario");
copy_keys(info, cfg, "era");
copy_keys(info, cfg, "modification");
depinfo_.add_child("modification", info);
}
BOOST_FOREACH (const config& cfg, gamecfg.child_range("era")) {
config info;
info["id"] = cfg["id"];
info["name"] = cfg["name"];
copy_keys(info, cfg, "scenario");
copy_keys(info, cfg, "modification", true);
depinfo_.add_child("era", info);
}
BOOST_FOREACH (const config& cfg, gamecfg.child_range("multiplayer")) {
config info;
info["id"] = cfg["id"];
info["name"] = cfg["name"];
copy_keys(info, cfg, "era");
copy_keys(info, cfg, "modification", true);
depinfo_.add_child("scenario", info);
}
}
void manager::save_state()
{
DBG_MP << "Saving current state" << std::endl;
prev_era_ = era_;
prev_scenario_ = scenario_;
prev_mods_ = mods_;
}
void manager::revert()
{
DBG_MP << "Restoring previous state" << std::endl;
era_ = prev_era_;
scenario_ = prev_scenario_;
mods_ = prev_mods_;
}
bool manager::exists(const elem& e) const
{
BOOST_FOREACH (const config& cfg, depinfo_.child_range(e.type)) {
if (cfg["id"] == e.id) {
return true;
}
}
return false;
}
std::vector<std::string>
manager::get_required_not_installed(const elem& e) const
{
std::vector<std::string> result;
std::vector<std::string> items = get_required(e);
BOOST_FOREACH (const std::string& str, items) {
if (!exists(elem(str, "modification"))) {
result.push_back(str);
}
}
return result;
}
std::vector<std::string> manager::get_required(const elem& e) const
{
std::vector<std::string> result;
if (e.type == "modification") {
return result;
}
config data = depinfo_.find_child(e.type, "id", e.id);
if (data.has_attribute("force_modification")) {
result = utils::split(data["force_modification"], ',');
}
return result;
}
std::vector<std::string> manager::get_required_not_enabled(const elem& e) const
{
std::vector<std::string> required = get_required(e);
std::vector<std::string> result;
BOOST_FOREACH (std::string str, required) {
if (!contains(mods_, str)) {
result.push_back(str);
}
}
return result;
}
std::vector<std::string> manager::get_conflicting_enabled(const elem& e) const
{
std::vector<std::string> result;
BOOST_FOREACH(const std::string& mod, mods_) {
if (conflicts(elem(mod, "modification"), e)) {
result.push_back(mod);
}
}
return result;
}
bool manager::conflicts(const elem& elem1, const elem& elem2, bool directonly) const
{
if (elem1 == elem2) {
return false;
}
// We ignore inexistent elements at this point, they will generate
// errors in change_era()/change_scenario() anyways.
if (!exists(elem1) || !exists(elem2)) {
return false;
}
config data1 = depinfo_.find_child(elem1.type, "id", elem1.id);
config data2 = depinfo_.find_child(elem2.type, "id", elem2.id);
// Whether we should skip the check entirely
if (data1.has_attribute("ignore_incompatible_" + elem2.type)) {
std::vector<std::string> ignored =
utils::split(data1["ignore_incompatible_" + elem2.type]);
if (contains(ignored, elem2.id)) {
return false;
}
}
if (data2.has_attribute("ignore_incompatible_" + elem1.type)) {
std::vector<std::string> ignored =
utils::split(data2["ignore_incompatible_" + elem1.type]);
if (contains(ignored, elem1.id)) {
return false;
}
}
bool result = false;
// Checking for direct conflicts between elem1 and elem2
if (data1.has_attribute("allow_" + elem2.type)) {
std::vector<std::string> allowed =
utils::split(data1["allow_" + elem2.type]);
result = !contains(allowed, elem2.id) && !requires(elem1, elem2);
} else if (data1.has_attribute("disallow_" + elem2.type)) {
std::vector<std::string> disallowed =
utils::split(data1["disallow_" + elem2.type]);
result = contains(disallowed, elem2.id);
}
if (data2.has_attribute("allow_" + elem1.type)) {
std::vector<std::string> allowed =
utils::split(data2["allow_" + elem1.type]);
result = result || (!contains(allowed, elem1.id) && !requires(elem2, elem1));
} else if (data2.has_attribute("disallow_" + elem1.type)) {
std::vector<std::string> disallowed =
utils::split(data2["disallow_" + elem1.type]);
result = result || contains(disallowed, elem1.id);
}
if (result) {
return true;
}
// Checking for indirect conflicts (i.e. conflicts between dependencies)
if (!directonly) {
std::vector<std::string> req1 = get_required(elem1),
req2 = get_required(elem2);
BOOST_FOREACH (const std::string& s, req1) {
elem m(s, "modification");
if (conflicts(elem2, m, true)) {
return true;
}
}
BOOST_FOREACH (const std::string& s, req2) {
elem m(s, "modification");
if (conflicts(elem1, m, true)) {
return true;
}
}
BOOST_FOREACH (const std::string& id1, req1) {
elem m1(id1, "modification");
BOOST_FOREACH (const std::string& id2, req2) {
elem m2(id2, "modification");
if (conflicts(m1, m2)) {
return true;
}
}
}
}
return false;
}
bool manager::requires(const elem& elem1, const elem& elem2) const
{
if (elem2.type != "modification") {
return false;
}
config data = depinfo_.find_child(elem1.type, "id", elem1.id);
if (data.has_attribute("force_modification")) {
std::vector<std::string> required =
utils::split(data["force_modification"]);
return contains(required, elem2.id);
}
return false;
}
void manager::try_era(const std::string& id, bool force)
{
save_state();
if (force) {
era_ = id;
} else if (!change_era(id)) {
revert();
}
}
void manager::try_scenario(const std::string& id, bool force)
{
save_state();
if (force) {
scenario_ = id;
} else if (!change_scenario(id)) {
revert();
}
}
void manager::try_modifications(const std::vector<std::string>& ids, bool force)
{
save_state();
if (force) {
mods_ = ids;
} else if (!change_modifications(ids)) {
revert();
}
}
void manager::try_era_by_index(int index, bool force)
{
try_era(depinfo_.child("era", index)["id"], force);
}
void manager::try_scenario_by_index(int index, bool force)
{
try_scenario(depinfo_.child("scenario", index - 1)["id"], force);
}
int manager::get_era_index() const
{
int result = 0;
BOOST_FOREACH (const config& i, depinfo_.child_range("era"))
{
if (i["id"] == era_) {
return result;
}
result++;
}
return -1;
}
int manager::get_scenario_index() const
{
int result = 1;
BOOST_FOREACH (const config& i, depinfo_.child_range("scenario"))
{
if (i["id"] == scenario_) {
return result;
}
result++;
}
return -1;
}
bool manager::enable_mods_dialog(const std::vector<std::string>& mods)
{
std::vector<std::string> items;
BOOST_FOREACH (const std::string& mod, mods) {
items.push_back(depinfo_.find_child("modification", "id", mod)["name"]);
}
gui2::tmp_depcheck_confirm_change dialog(true, items, _("A component"));
return dialog.show(video_);
}
bool manager::disable_mods_dialog(const std::vector<std::string>& mods)
{
std::vector<std::string> items;
BOOST_FOREACH (const std::string& mod, mods) {
items.push_back(depinfo_.find_child("modification", "id", mod)["name"]);
}
gui2::tmp_depcheck_confirm_change dialog(false, items, _("A component"));
return dialog.show(video_);
}
std::string manager::change_era_dialog(const std::vector<std::string>& eras)
{
std::vector<std::string> items;
BOOST_FOREACH(const std::string& era, eras) {
items.push_back(depinfo_.find_child("era", "id", era)["name"]);
}
gui2::tmp_depcheck_select_new dialog(ERA, items);
if (dialog.show(video_)) {
return eras[dialog.result()];
}
return "";
}
std::string
manager::change_scenario_dialog(const std::vector<std::string>& scenarios)
{
std::vector<std::string> items;
BOOST_FOREACH (const std::string& scenario, scenarios) {
items.push_back(depinfo_.find_child("scenario", "id", scenario)["name"]);
}
gui2::tmp_depcheck_select_new dialog(SCENARIO, items);
if (dialog.show(video_)) {
return scenarios[dialog.result()];
}
return "";
}
void manager::failure_dialog(const std::string& msg)
{
gui2::show_message
(video_, _("Failed to resolve dependencies"), msg, _("OK"));
}
void manager::insert_element(component_type type, const config& data, int index)
{
std::string type_str;
switch (type) {
case ERA:
type_str = "era";
break;
case SCENARIO:
type_str = "scenario";
break;
case MODIFICATION:
type_str = "modification";
}
depinfo_.add_child_at(type_str, data, index);
}
bool manager::change_scenario(const std::string& id)
{
// Checking for missing dependencies
if (!get_required_not_installed(elem(id, "scenario")).empty()) {
std::string msg =
_("Scenario can't be activated. Some dependencies are missing: ");
msg +=
utils::join(get_required_not_installed(elem(id, "scenario")), ", ");
failure_dialog(msg);
return false;
}
scenario_ = id;
elem scen = elem(id, "scenario");
// Firstly, we check if we have to enable/disable any mods
std::vector<std::string> req = get_required_not_enabled(scen);
std::vector<std::string> con = get_conflicting_enabled(scen);
if (!req.empty()) {
if (!enable_mods_dialog(req)) {
return false;
}
}
if (!con.empty()) {
if (!disable_mods_dialog(con)) {
return false;
}
}
std::vector<std::string> newmods = req;
BOOST_FOREACH (const std::string& i, mods_) {
if (!contains(con, i)) {
newmods.push_back(i);
}
}
mods_ = newmods;
// Now checking if the currently selected era conflicts the scenario
// and changing era if necessary
if (!conflicts(scen, elem(era_, "era"))) {
return true;
}
std::vector<std::string> compatible;
BOOST_FOREACH (const config& i, depinfo_.child_range("era")) {
if (!conflicts(scen, elem(i["id"], "era"))) {
compatible.push_back(i["id"]);
}
}
if (!compatible.empty()) {
era_ = change_era_dialog(compatible);
} else {
failure_dialog(_("No compatible eras found."));
return false;
}
if (era_.empty()) {
return false;
}
return change_era(era_);
}
bool manager::change_era(const std::string& id)
{
// Checking for missing dependenciess
if (!get_required_not_installed(elem(id, "era")).empty()) {
std::string msg =
_("Era can't be activated. Some dependencies are missing: ");
msg += utils::join(get_required_not_installed(elem(id, "era")), ", ");
failure_dialog(msg);
return false;
}
era_ = id;
elem era = elem(id, "era");
std::vector<std::string> req = get_required_not_enabled(era);
std::vector<std::string> con = get_conflicting_enabled(era);
// Firstly, we check if we have to enable/disable any mods
if (!req.empty()) {
if (!enable_mods_dialog(req)) {
return false;
}
}
if (!con.empty()) {
if (!disable_mods_dialog(con)) {
return false;
}
}
std::vector<std::string> newmods = req;
BOOST_FOREACH (const std::string& i, mods_) {
if (!contains(con, i)) {
newmods.push_back(i);
}
}
mods_ = newmods;
// Now checking if the currently selected scenarop conflicts the era
// and changing scenario if necessary
if (!conflicts(era, elem(scenario_, "scenario"))) {
return true;
}
std::vector<std::string> compatible;
BOOST_FOREACH (const config& i, depinfo_.child_range("scenario")) {
if (!conflicts(era, elem(i["id"], "scenario"))) {
compatible.push_back(i["id"]);
}
}
if (!compatible.empty()) {
scenario_ = change_scenario_dialog(compatible);
} else {
failure_dialog(_("No compatible scenarios found."));
return false;
}
if (scenario_.empty()) {
return false;
}
return change_scenario(scenario_);
}
bool manager::change_modifications
(const std::vector<std::string>& modifications)
{
// Checking if the selected combination of mods is valid at all
std::vector<std::string> filtered;
BOOST_FOREACH (const std::string& i, modifications) {
bool ok = true;
elem ei(i, "modification");
BOOST_FOREACH (const std::string& j, filtered) {
ok = ok && !conflicts(ei, elem(j, "modification"));
}
if (ok) {
filtered.push_back(i);
}
}
if (filtered.size() != modifications.size()) {
failure_dialog(_("Not all of the chosen modifications are compatible." \
" Some of them will be disabled."));
}
mods_ = filtered;
// Checking if the currently selected era is compatible with the set
// modifications, and changing era if necessary
std::vector<std::string> compatible;
BOOST_FOREACH (const config& c, depinfo_.child_range("era")) {
elem era(c["id"], "era");
bool ok = true;
BOOST_FOREACH (const std::string& s, mods_) {
ok = ok && !conflicts(era, elem(s, "modification"));
}
if (ok) {
compatible.push_back(era.id);
}
}
if (!contains(compatible, era_)) {
if (!compatible.empty()) {
era_ = change_era_dialog(compatible);
} else {
failure_dialog(_("No compatible eras found."));
return false;
}
if (era_.empty()) {
return false;
}
if (!change_era(era_)) {
return false;
}
} else {
if (!change_era(era_)) {
return false;
}
}
compatible.clear();
// Checking if the currently selected scenario is compatible with
// the set modifications, and changing scenario if necessary
BOOST_FOREACH (const config& c, depinfo_.child_range("scenario")) {
elem scen(c["id"], "scenario");
bool ok = true;
BOOST_FOREACH (const std::string& s, mods_) {
ok = ok && !conflicts(scen, elem(s, "modification"));
}
if (ok) {
compatible.push_back(scen.id);
}
}
if (!contains(compatible, scenario_)) {
if (!compatible.empty()) {
scenario_ = change_scenario_dialog(compatible);
} else {
failure_dialog(_("No compatible scenarios found."));
return false;
}
if (scenario_.empty()) {
return false;
}
return change_scenario(scenario_);
} else {
if (!change_scenario(scenario_)) {
return false;
}
}
return true;
}
} //namespace depcheck
} //namespace mp

337
src/mp_depcheck.hpp Normal file
View File

@ -0,0 +1,337 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#ifndef MP_DEPCHECK_HPP_INCLUDED
#define MP_DEPCHECK_HPP_INCLUDED
#include <string>
#include <vector>
#include <map>
#include "config.hpp"
#include "game_display.hpp"
namespace mp
{
namespace depcheck
{
enum component_type
{
ERA,
SCENARIO,
MODIFICATION
};
/**
* Note to all triers:
* It's not guaranteed that the specified component will be selected
* (if the user denies to perform dependency resolution, all changes
* will be reverted). Consequently, it's essential to check the
* selected values after calling any trier.
*
* Note to ctor & insert_element:
* Please note that the ctor collects data for scenario elements from
* "multiplayer" nodes, while insert_element from "scenario" nodes.
*/
class manager
{
public:
manager(const config& gamecfg, CVideo& video);
/**
* Tries to set the selected era
*
* @param id the id of the era
* @param force whether to skip dependency check
*/
void try_era(const std::string& id, bool force = false);
/**
* Tries to set the selected scenario
*
* @param id the id of the scenario
* @param force whether to skip dependency check
*/
void try_scenario(const std::string& id, bool force = false);
/**
* Tries to set the enabled modifications
*
* @param ids the ids of the modifications
* @param force whether to skip dependency check
*/
void try_modifications(const std::vector<std::string>& ids,
bool force = false );
/**
* Tries to set the selected era
*
* @param index the index of the era
* @param force whether to skip dependency check
*/
void try_era_by_index(int index, bool force = false);
/**
* Tries to set the selected scenario
*
* @param index the index of the scenario
* @param force whether to skip dependency check
*/
void try_scenario_by_index(int index, bool force = false);
/**
* Returns the selected era
*
* @return the id of the era
*/
const std::string& get_era() const { return era_; }
/**
* Returns the selected scenario
*
* @return the id of the scenario
*/
const std::string& get_scenario() const { return scenario_; }
/**
* Returns the enabled modifications
*
* @return the ids of the modifications
*/
const std::vector<std::string>& get_modifications() const { return mods_; }
/**
* Returns the selected era
*
* @return the index of the era
*/
int get_era_index() const;
/**
* Returns the selected scenario
*
* @return the index of the scenario
*/
int get_scenario_index() const;
/**
* Adds a new element to the manager's database
*
* @param type the type of the element
* @param data a config object containing the dependency info for the
* element
* @param index where to insert the element
*/
void insert_element(component_type type, const config& data, int index = 0);
private:
/** represents a component (era, modification or scenario)*/
struct elem {
elem(const std::string& _id, const std::string& _type)
: id(_id)
, type(_type)
{}
std::string id;
std::string type;
bool operator ==(const elem& e) const
{ return id == e.id && type == e.type; }
bool operator !=(const elem& e) const { return !(*this == e); }
};
/** the screen to display dialogs on */
CVideo& video_;
/** holds all required info about the components and their dependencies */
config depinfo_;
/** the id of the currently selected era */
std::string era_;
/** the id of the currently selected scenario */
std::string scenario_;
/** the ids of the currently selected modifications */
std::vector<std::string> mods_;
/** used by save_state() and revert() to backup/restore era_ */
std::string prev_era_;
/** used by save_state() and revert() to backup/restore scenario_ */
std::string prev_scenario_;
/** used by save_state() and revert() to backup/restore mods_ */
std::vector<std::string> prev_mods_;
/** saves the current values of era_, scenarios_ and mods_ */
void save_state();
/** restores the lastly saved values of era_, scenarios_ and mods_ */
void revert();
/**
* Attempts to change the selected scenario.
*
* @param id the scenario's id
* @return true if the selection was changed; false if not
*/
bool change_scenario(const std::string& id);
/**
* Attempts to change the selected era.
*
* @param id the era's id
* @return true if the selection was changed; false if not
*/
bool change_era(const std::string& id);
/**
* Attempts to change the selected modifications.
*
* @param modifications the list of the modifications' ids
* @return true if the selection was changed; false if not
*/
bool change_modifications(const std::vector<std::string>& modifications);
/**
* Decides if two components are conflicting or not
*
* @param elem1 the first component
* @param elem2 the second component
* @param directonly whether the function should ignore any possible
* conflicts between the components' dependencies.
*
* @return true if e1 and e2 conflict, false if not
*/
bool conflicts(const elem& elem1, const elem& elem2, bool directonly=false) const;
/**
* Decides whether e1 requires e2
*
* @param elem1 a component; by definition, passing a modification here
* makes no sense
* @param elem2 another component; by definition, passing anything else
* than a modification here makes no sense
*
* @return true if e2 is required by e1, false if not
*/
bool requires(const elem& elem1, const elem& elem2) const;
/**
* Get the list of modifications required by a certain component
*
* @param e the component
*
* @return the list of the modifications' ids
*/
std::vector<std::string> get_required(const elem& e) const;
/**
* Get the list of modifications which are required by a certain
* component, but aren't currently enabled
*
* @param e the component
*
* @return the list of the modifications' ids
*/
std::vector<std::string> get_required_not_enabled(const elem& e) const;
/**
* Get the list of modifications which are conflicting a certain
* component and are currently enabled
*
* @param e the component
*
* @return the list of the modifications' ids
*/
std::vector<std::string> get_conflicting_enabled(const elem& e) const;
/**
* Get the list of modifications which are required by a certain
* component, but currently unavailable on the computer
*
* @param e the component
*
* @return the list of the modifications' ids
*/
std::vector<std::string> get_required_not_installed(const elem& e) const;
/**
* Display a dialog requesting confirmation for enabling some
* modifications
*
* @param mods the list of modifications to be enabled
*
* @return true, if the user accepted the change, false if not
*/
bool enable_mods_dialog(const std::vector<std::string>& mods);
/**
* Display a dialog requesting confirmation for disabling some
* modifications
*
* @param mods the list of modifications to be disabled
*
* @return true, if the user accepted the change, false if not
*/
bool disable_mods_dialog(const std::vector<std::string>& mods);
/**
* Display a dialog requesting the user to select a new era
*
* @param eras the possible options (ids)
*
* @return the selected era's id or empty string if the user
* refused to select any
*/
std::string change_era_dialog(const std::vector<std::string>& eras);
/**
* Display a dialog requesting the user to select a new scenario
*
* @param scenarios the possible options (ids)
*
* @return the selected scenario's id or empty string if the user
* refused to select any
*/
std::string change_scenario_dialog
(const std::vector<std::string>& scenarios);
/**
* Shows an error message
*
* @param msg the message to be displayed
*/
void failure_dialog(const std::string& msg);
/**
* Decides whether a certain component is installed or not
*
* @param e the component
*
* @return true if the component exists false if not
*/
bool exists(const elem& e) const;
};
} //namespace depcheck
} //namespace mp
#endif

View File

@ -20,6 +20,7 @@
*/
#include "mp_game_settings.hpp"
#include "formula_string_utils.hpp"
mp_game_settings::mp_game_settings() :
savegame_config(),
@ -28,6 +29,7 @@ mp_game_settings::mp_game_settings() :
hash(),
mp_era(),
mp_scenario(),
active_mods(),
village_gold(0),
village_support(1),
xp_modifier(0),
@ -45,6 +47,7 @@ mp_game_settings::mp_game_settings() :
share_view(false),
share_maps(false),
saved_game(false),
options(),
scenario_data()
{ reset(); }
@ -56,6 +59,7 @@ mp_game_settings::mp_game_settings(const config& cfg) :
hash(),
mp_era(),
mp_scenario(),
active_mods(),
village_gold(0),
village_support(1),
xp_modifier(0),
@ -73,6 +77,7 @@ mp_game_settings::mp_game_settings(const config& cfg) :
share_view(false),
share_maps(false),
saved_game(false),
options(),
scenario_data()
{
set_from_config(cfg);
@ -85,6 +90,7 @@ mp_game_settings::mp_game_settings(const mp_game_settings& settings)
, hash(settings.hash)
, mp_era(settings.mp_era)
, mp_scenario(settings.mp_scenario)
, active_mods(settings.active_mods)
, village_gold(settings.village_gold)
, village_support(settings.village_support)
, xp_modifier(settings.xp_modifier)
@ -102,6 +108,7 @@ mp_game_settings::mp_game_settings(const mp_game_settings& settings)
, share_view(settings.share_view)
, share_maps(settings.share_maps)
, saved_game(settings.saved_game)
, options(settings.options)
, scenario_data(settings.scenario_data)
{
}
@ -116,6 +123,7 @@ void mp_game_settings::set_from_config(const config& game_cfg)
hash = cfg["hash"].str();
mp_era = cfg["mp_era"].str();
mp_scenario = cfg["mp_scenario"].str();
active_mods = utils::split(cfg["active_mods"], ',');
xp_modifier = cfg["experience_modifier"];
use_map_settings = cfg["mp_use_map_settings"].to_bool();
fog_game = cfg["mp_fog"].to_bool();
@ -130,6 +138,7 @@ void mp_game_settings::set_from_config(const config& game_cfg)
allow_observers = cfg["observer"].to_bool();
shuffle_sides = cfg["shuffle_sides"].to_bool();
saved_game = cfg["savegame"].to_bool();
options = cfg.child_or_empty("options");
}
void mp_game_settings::reset()
@ -139,6 +148,7 @@ void mp_game_settings::reset()
hash = "";
mp_era = "";
mp_scenario = "";
active_mods.clear();
village_gold = 0;
village_support = 1;
xp_modifier = 0;
@ -148,6 +158,7 @@ void mp_game_settings::reset()
mp_countdown_action_bonus=0;
mp_countdown=false;
use_map_settings = random_start_time = fog_game = shroud_game = allow_observers = shuffle_sides = share_view = share_maps = false;
options.clear();
scenario_data.clear();
}
@ -160,6 +171,7 @@ config mp_game_settings::to_config() const
cfg["hash"] = hash;
cfg["mp_era"] = mp_era;
cfg["mp_scenario"] = mp_scenario;
cfg["active_mods"] = utils::join(active_mods, ",");
cfg["experience_modifier"] = xp_modifier;
cfg["mp_countdown"] = mp_countdown;
cfg["mp_countdown_init_time"] = mp_countdown_init_time;
@ -174,6 +186,7 @@ config mp_game_settings::to_config() const
cfg["observer"] = allow_observers;
cfg["shuffle_sides"] = shuffle_sides;
cfg["savegame"] = saved_game;
cfg.add_child("options", options);
return cfg;
}

View File

@ -39,6 +39,7 @@ struct mp_game_settings : public savegame::savegame_config
std::string hash;
std::string mp_era;
std::string mp_scenario;
std::vector<std::string> active_mods;
int village_gold;
int village_support;
@ -59,6 +60,8 @@ struct mp_game_settings : public savegame::savegame_config
bool saved_game;
config options;
/**
* If the game is to be randomly generated, the map generator
* will create the scenario data in this variable

632
src/mp_options.cpp Normal file
View File

@ -0,0 +1,632 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#include "mp_options.hpp"
#include "gettext.hpp"
#include "gui/auxiliary/window_builder.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/slider.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/toggle_button.hpp"
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
static lg::log_domain log_mp_create_options("mp/create/options");
#define DBG_MP LOG_STREAM(debug, log_mp_create_options)
namespace mp
{
namespace options
{
config to_event(const config& cfg)
{
config result;
if (!cfg) {
return result;
}
result["name"] = "prestart";
BOOST_FOREACH (const config& c, cfg.child_range("option")) {
config ev;
ev["name"] = c["id"];
ev["value"] = c["value"];
result.add_child("set_variable", ev);
}
return result;
}
void manager::init_info(const config& cfg, const std::string& key)
{
BOOST_FOREACH (const config& comp, cfg.child_range(key)) {
config entry;
entry["id"] = comp["id"];
entry["name"] = comp["name"];
if (comp.has_child("options")) {
const config& options = comp.child("options");
BOOST_FOREACH (const config::any_child& c,
options.all_children_range()) {
entry.add_child(c.key, c.cfg);
}
}
// We need to store components even if they don't have any options in
// order to have set_xxx_by_index work properly
options_info_.add_child(key, entry);
}
}
manager::manager(const config& gamecfg, CVideo& video, const config& values)
: options_info_()
, values_(values)
, video_(video)
, era_()
, scenario_()
, modifications_()
{
DBG_MP << "Initializing the options manager" << std::endl;
init_info(gamecfg, "modification");
init_info(gamecfg, "era");
init_info(gamecfg, "multiplayer");
BOOST_FOREACH (const config::any_child& i,
options_info_.all_children_range())
{
BOOST_FOREACH (const config::any_child& j, i.cfg.all_children_range())
{
if (is_valid_option(j.key, j.cfg)) {
config& value = get_value_cfg(j.cfg["id"]);
value["value"] = get_stored_value(j.cfg["id"]);
}
}
}
}
void manager::set_values(const config& c)
{
values_ = c;
}
void manager::set_era(const std::string& era)
{
era_ = era;
}
void manager::set_era_by_index(int index)
{
era_ = options_info_.child("era", index)["id"].str();
}
void manager::set_scenario(const std::string& scenario)
{
scenario_ = scenario;
}
void manager::set_scenario_by_index(int index)
{
scenario_ = options_info_.child("multiplayer", index - 1)["id"].str();
}
void manager::set_modifications(const std::vector<std::string>& modifications)
{
modifications_ = modifications;
}
void manager::insert_element(elem_type type, const config& data, int pos)
{
switch (type)
{
case SCENARIO:
options_info_.add_child_at("multiplayer", data, pos);
break;
case ERA:
options_info_.add_child_at("era", data, pos);
break;
case MODIFICATION:
options_info_.add_child_at("modification", data, pos);
break;
}
}
void manager::show_dialog()
{
DBG_MP << "Building the options dialog" << std::endl;
// Constructing the dialog
config dialog_cfg;
dialog_cfg.add_child("resolution");
dialog_cfg["definition"] = "default";
dialog_cfg["automatic_placement"] = true;
dialog_cfg["vertical_placement"] = "center";
dialog_cfg["horizontal_placement"] = "center";
dialog_cfg.add_child("helptip")["id"] = "tooltip_large";
dialog_cfg.add_child("tooltip")["id"] = "tooltip_large";
config& grid = dialog_cfg.add_child("grid");
// Adding widgets for available options
add_widgets(options_info_.find_child("era", "id", era_), grid);
add_widgets(options_info_.find_child("multiplayer", "id", scenario_), grid);
for (unsigned i = 0; i<modifications_.size(); i++) {
add_widgets(
options_info_.find_child("modification", "id", modifications_[i]),
grid);
}
if (grid.ordered_begin() == grid.ordered_end()) {
// No widgets were actually added, we've got nothing to show
gui2::show_transient_message(video_, "", _(
"None of the selected modifications, era or scenario provide " \
"configuration options."));
return;
}
// Dialog buttons
config& row = grid.add_child("row");
row["grow_factor"] = 0;
config& column = row.add_child("column");
column["grow_factor"] = 1;
column["border"] = "all";
column["border_size"] = 5;
column["horizontal_alignment"] = "right";
config& widget_grid = column.add_child("grid");
config& widget_row = widget_grid.add_child("row");
widget_row["grow_factor"] = 0;
config& defaults_column = widget_row.add_child("column");
defaults_column["grow_factor"] = 0;
defaults_column["border"] = "all";
defaults_column["border_size"] = 5;
defaults_column["horizontal_alignment"] = "left";
config& ok_column = widget_row.add_child("column");
ok_column["grow_factor"] = 0;
ok_column["border"] = "all";
ok_column["border_size"] = 5;
ok_column["horizontal_alignment"] = "right";
config& cancel_column = widget_row.add_child("column");
cancel_column["grow_factor"] = 0;
cancel_column["border"] = "all";
cancel_column["border_size"] = 5;
cancel_column["horizontal_alignment"] = "left";
config& defaults_button = defaults_column.add_child("button");
defaults_button["definition"] = "default";
defaults_button["label"] = _("Restore defaults");
defaults_button["id"] = "restore_defaults";
config& ok_button = ok_column.add_child("button");
ok_button["definition"] = "default";
ok_button["label"] = _("OK");
ok_button["id"] = "ok";
config& cancel_button = cancel_column.add_child("button");
cancel_button["definition"] = "default";
cancel_button["label"] = _("Cancel");
cancel_button["id"] = "cancel";
// Building the window
gui2::twindow_builder::tresolution resolution(dialog_cfg);
gui2::twindow* window = gui2::build(video_, &resolution);
__tmp_set_checkbox_defaults(window);
gui2::tbutton* button = gui2::find_widget<gui2::tbutton>
(window, "restore_defaults", false, true);
// Callbacks (well, one callback)
button->connect_click_handler(boost::bind(restore_defaults, this, window));
// Show window
DBG_MP << "Showing the dialog" << std::endl;
if (window->show() == gui2::twindow::CANCEL) {
DBG_MP << "User cancelled changes" << std::endl;
delete window;
return;
}
// Saving the results
DBG_MP << "User accepted changes, saving values" << std::endl;
extract_values("era", era_, window);
extract_values("multiplayer", scenario_, window);
for (unsigned i = 0; i<modifications_.size(); i++) {
extract_values("modification", modifications_[i], window);
}
delete window;
}
void manager::add_widgets(const config& data, config& grid) const
{
if (!data.has_child("entry") &&
!data.has_child("slider") &&
!data.has_child("checkbox"))
{
//Don't display the title if there're no options at all
return;
}
{
// The title for this section
config& row = grid.add_child("row");
row["grow_factor"] = 0;
config& column = row.add_child("column");
column["grow_factor"] = 1;
column["border"] = "all";
column["border_size"] = 5;
column["horizontal_alignment"] = "left";
config& caption = column.add_child("label");
caption["definition"] = "title";
caption["label"] = data["name"];
}
// Adding the widgets
BOOST_FOREACH (const config::any_child& c, data.all_children_range()) {
if (!is_valid_option(c.key, c.cfg))
{
continue;
}
config& row = grid.add_child("row");
row["grow_factor"] = 0;
config& column = row.add_child("column");
column["grow_factor"] = 1;
column["border"] = "all";
column["border_size"] = 5;
column["horizontal_alignment"] = "left";
if (c.key == "entry") {
add_entry(c.cfg, column);
} else if (c.key == "slider") {
add_slider(c.cfg, column);
} else if (c.key == "checkbox") {
add_checkbox(c.cfg, column);
}
}
}
void manager::add_entry(const config& data, config& column) const
{
config& grid = column.add_child("grid");
config& row = grid.add_child("row");
row["grow_factor"] = 0;
config& label_column = row.add_child("column");
label_column["grow_factor"] = 0;
label_column["border"] = "all";
label_column["border_size"] = 5;
label_column["horizontal_alignment"] = "left";
config& entry_column = row.add_child("column");
entry_column["grow_factor"] = 0;
entry_column["border"] = "all";
entry_column["border_size"] = 5;
entry_column["horizontal_alignment"] = "left";
config& label = label_column.add_child("label");
label["definition"] = "default";
label["label"] = data["description"];
config& entry = entry_column.add_child("text_box");
entry["id"] = data["id"];
entry["definition"] = "default";
entry["label"] = get_stored_value(data["id"]);
}
void manager::add_slider(const config& data, config& column) const
{
config& grid = column.add_child("grid");
config& row = grid.add_child("row");
row["grow_factor"] = 0;
config& label_column = row.add_child("column");
label_column["grow_factor"] = 0;
label_column["border"] = "all";
label_column["border_size"] = 5;
label_column["horizontal_alignment"] = "left";
config& slider_column = row.add_child("column");
slider_column["grow_factor"] = 0;
slider_column["border"] = "all";
slider_column["border_size"] = 5;
slider_column["horizontal_alignment"] = "left";
config& label = label_column.add_child("label");
label["definition"] = "default";
label["label"] = data["description"];
config& slider = slider_column.add_child("slider");
slider["id"] = data["id"];
slider["definition"] = "default";
slider["minimum_value"] = data["min_value"];
slider["maximum_value"] = data["max_value"];
slider["step_size"] = data["step"].to_int() ? data["step"].to_int() : 1;
// This seems to misbehave when step_size isn't 1, apparently a bug
// with the slider widget itself.
slider["value"] = get_stored_value(data["id"]);
}
void manager::add_checkbox(const config& data, config& column) const
{
config& grid = column.add_child("grid");
config& row = grid.add_child("row");
row["grow_factor"] = 0;
config& box_column = row.add_child("column");
box_column["grow_factor"] = 0;
box_column["border"] = "all";
box_column["border_size"] = 5;
box_column["horizontal_alignment"] = "left";
config& checkbox = box_column.add_child("toggle_button");
checkbox["id"] = data["id"];
checkbox["definition"] = "default";
checkbox["label"] = data["description"];
}
config& manager::get_value_cfg(const std::string& id)
{
{
const manager* m = this;
config& value_cfg = const_cast<config&>(m->get_value_cfg(id));
if (!value_cfg.empty()) {
return value_cfg;
}
}
config::any_child info = get_option_parent(id);
config* parent_cfg;
if (!values_.find_child(info.key, "id", info.cfg["id"])) {
parent_cfg = &values_.add_child(info.key);
(*parent_cfg)["id"] = info.cfg["id"];
} else {
parent_cfg = &values_.find_child(info.key, "id", info.cfg["id"]);
}
config& value_cfg = parent_cfg->add_child("option");
value_cfg["id"] = id;
return value_cfg;
}
const config& manager::get_value_cfg(const std::string& id) const
{
static const config empty;
BOOST_FOREACH (const config::any_child& i, values_.all_children_range()) {
BOOST_FOREACH (const config& j, i.cfg.child_range("option")) {
if (j["id"] == id) {
return j;
}
}
}
return empty;
}
config::any_child manager::get_option_parent(const std::string& id) const
{
static const config empty;
static const std::string empty_key = "";
static config::any_child not_found(&empty_key, &empty);
BOOST_FOREACH (const config::any_child& i,
options_info_.all_children_range()) {
BOOST_FOREACH (const config::any_child& j, i.cfg.all_children_range()) {
if (j.cfg["id"] == id) {
return i;
}
}
}
return not_found;
}
const config& manager::get_option_info_cfg(const std::string& id) const
{
static const config empty;
BOOST_FOREACH (const config::any_child& i,
options_info_.all_children_range()) {
BOOST_FOREACH (const config::any_child& j, i.cfg.all_children_range()) {
if (j.cfg["id"] == id) {
return j.cfg;
}
}
}
return empty;
}
config::attribute_value manager::get_stored_value(const std::string& id) const
{
const config& valcfg = get_value_cfg(id);
if (!valcfg["value"].empty()) {
// There's a saved value for this option
return valcfg["value"];
}
// Fall back to the option's default
return get_default_value(id);
}
config::attribute_value manager::get_default_value(const std::string& id) const
{
const config& optinfo = get_option_info_cfg(id);
return optinfo["default"];
}
int manager::get_slider_value(const std::string& id, gui2::twindow* win) const
{
gui2::tslider* widget =
gui2::find_widget<gui2::tslider>(win, id, false, true);
return widget->get_value();
}
bool manager::get_checkbox_value
(const std::string& id, gui2::twindow* win) const
{
gui2::ttoggle_button* widget =
gui2::find_widget<gui2::ttoggle_button>(win, id, false, true);
return widget->get_value();
}
std::string manager::get_entry_value(const std::string& id,
gui2::twindow* window) const
{
gui2::ttext_box* widget =
gui2::find_widget<gui2::ttext_box>(window, id, false, true);
return widget->text();
}
void manager::set_slider_value(int val, const std::string& id,
gui2::twindow* win) const
{
gui2::tslider* widget =
gui2::find_widget<gui2::tslider>(win, id, false, true);
widget->set_value(val);
}
void manager::set_checkbox_value(bool val, const std::string& id,
gui2::twindow* win) const
{
gui2::ttoggle_button* widget =
gui2::find_widget<gui2::ttoggle_button>(win, id, false, true);
widget->set_value(val);
}
void manager::set_entry_value(const std::string& val, const std::string& id,
gui2::twindow* win) const
{
gui2::ttext_box* widget =
gui2::find_widget<gui2::ttext_box>(win, id, false, true);
widget->set_value(val);
}
void manager::extract_values(const std::string& key, const std::string& id,
gui2::twindow* window)
{
BOOST_FOREACH (const config::any_child& c,
options_info_.find_child(key, "id", id).all_children_range())
{
if (!is_valid_option(c.key, c.cfg)) {
continue;
}
config& out = get_value_cfg(c.cfg["id"].str());
if (c.key == "entry") {
out["value"] = get_entry_value(c.cfg["id"], window);
} else if (c.key == "slider") {
out["value"] = get_slider_value(c.cfg["id"], window);
} else if (c.key == "checkbox") {
out["value"] = get_checkbox_value(c.cfg["id"], window);
}
}
}
bool manager::is_valid_option(const std::string& key, const config& option)
{
return (key == "slider" || key == "entry" || key == "checkbox") &&
(!option["id"].empty());
}
void manager::restore_defaults(manager* m, gui2::twindow* w)
{
const config& era = m->options_info_.find_child("era", "id", m->era_);
restore_defaults_for_component(era, m, w);
const config& scen = m->options_info_.find_child("multiplayer", "id",
m->scenario_);
restore_defaults_for_component(scen, m, w);
BOOST_FOREACH (const std::string& id, m->modifications_) {
const config& mod = m->options_info_.find_child("modification", "id",
id);
restore_defaults_for_component(mod, m, w);
}
}
void manager::restore_defaults_for_component(const config& c, manager* m,
gui2::twindow* w)
{
BOOST_FOREACH (const config::any_child& i, c.all_children_range()) {
if (!is_valid_option(i.key, i.cfg)) {
continue;
}
const std::string id = i.cfg["id"].str();
if (i.key == "entry") {
m->set_entry_value(m->get_default_value(id).str(), id, w);
} else if (i.key == "checkbox") {
m->set_checkbox_value(m->get_default_value(id).to_bool(), id, w);
} else if (i.key == "slider") {
m->set_slider_value(m->get_default_value(id).to_int(), id, w);
}
}
}
void manager::__tmp_set_checkbox_defaults(gui2::twindow* window) const
{
BOOST_FOREACH (const config::any_child& i,
options_info_.all_children_range())
{
BOOST_FOREACH (const config& j, i.cfg.child_range("checkbox"))
{
if (!is_valid_option("checkbox", j)) {
continue;
}
gui2::ttoggle_button* button;
button = gui2::find_widget<gui2::ttoggle_button>
(window, j["id"].str(), false, false);
if (button) {
button->set_value(get_stored_value(j["id"]).to_bool());
}
}
}
}
} // namespace options
} // namespace mp

370
src/mp_options.hpp Normal file
View File

@ -0,0 +1,370 @@
/*
Copyright (C) 2012 by Boldizsár Lipka <lipka.boldizsar@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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.
See the COPYING file for more details.
*/
#ifndef MP_OPTIONS_HPP_INCLUDED
#define MP_OPTIONS_HPP_INCLUDED
#include <string>
#include <map>
#include "config.hpp"
#include "video.hpp"
#include "gui/widgets/widget.hpp"
#include "gui/widgets/window.hpp"
namespace mp
{
namespace options
{
config to_event(const config& options);
// TODO: there's an identical enum in mp_depcheck.hpp, maybe we should factor
// out?
enum elem_type
{
SCENARIO,
ERA,
MODIFICATION
};
class manager
{
public:
/**
* Constructor.
*
* @param gamecfg The config object holding all eras, scenarios
* and modifications.
*
* @param video The screen to display the dialog on.
*
* @param initial_values The initial values for each option.
*/
manager(const config& gamecfg, CVideo& video, const config& initial_values);
/**
* Set the current values the options. This overrides ALL previously set
* values, even if a not all options are provided a new value for.
*
* @param values The new values for each option.
*/
void set_values(const config& values);
/**
* Sets the selected era. Whenever show_dialog is called, only
* options for the selected era will be displayed.
*
* @param id The era's id.
*/
void set_era(const std::string& id);
/**
* Sets the selected era. Whenever show_dialog is called, only
* options for the selected era will be displayed.
*
* @param index The era's index.
*/
void set_era_by_index(int index);
/**
* Sets the selected scenario. Whenever show_dialog is called, only
* options for the selected scenario will be displayed.
*
* @param id The scenario's id.
*/
void set_scenario(const std::string& id);
/**
* Sets the selected scenario. Whenever show_dialog is called, only
* options for the selected scenario will be displayed.
*
* @param index The scenario's index.
*/
void set_scenario_by_index(int index);
/**
* Sets the activated modifications. Whenever show_dialog is called, only
* options for the activated modifications will be displayed.
*
* @param ids The ids of the modifications
*/
void set_modifications(const std::vector<std::string>& ids);
/**
* Add options information of an era/scenario/modification not yet in the
* database.
*
* @param type The type of the element,
*
* @param data The config object which holds the
* information about the element's options in
* an [options] child.
*
* @param pos The position to insert the element into.
*/
void insert_element(elem_type type, const config& data, int pos);
/**
* Shows the options dialog and saves the selected values.
*/
void show_dialog();
/**
* Returns the the values for each option.
*
* @return A config containing the values.
*/
const config& get_values() const { return values_; }
private:
/** Stores needed info about each element and their configuration options */
config options_info_;
/** Stores the selected values for each option */
config values_;
/** The screen to display the dialog on */
CVideo &video_;
/** The id of the selected era */
std::string era_;
/** The id of the selected scenario */
std::string scenario_;
/** The ids of the selected modifications */
std::vector<std::string> modifications_;
/**
* Adds the necessary information about the specified component
* to options_info_.
*
* @param cfg The component's data.
* @param key The component's type.
*/
void init_info(const config& cfg, const std::string& key);
/**
* Creates a widget layout based on an [options] section.
*
* @param data The [options] section.
* @param grid The grid to create the layout in.
*/
void add_widgets(const config& data, config& grid) const;
/**
* Creates a slider widget.
*
* @param data A [slider] config.
* @param column The grid cell to add the widget into.
*/
void add_slider(const config& data, config& column) const;
/**
* Creates a checkbox (toggle button) widget.
*
* @param data A [checkbox] config.
* @param column The grid cell to add the widget into.
*/
void add_checkbox(const config& data, config& column) const;
/**
* @todo Implement this function (along with a combo box widget, preferably)
*
* Creates a combo box widget.
*
* @param data A [combobox] config.
* @param column The grid cell to add the widget into.
*/
void add_combobox(const config& data, config& column) const;
/**
* Creates a text entry widget.
*
* @param data An [entry] config.
* @param column The grid cell to add the widget into.
*/
void add_entry(const config& data, config& column) const;
/**
* Returns the node which holds the selected value of an option. If that
* node is not yet created, the function creates it.
*
* @param id The id of the option.
*
* @return A reference to the config which the value
* for this option should be written into.
*/
config& get_value_cfg(const std::string& id);
/**
* Returns the node which holds the selected value of an option. If that
* node is not yet created, the function returns an empty config.
*
* @param id The id of the option.
*
* @return A reference to the config which the value
* for this option should be written into or
* an empty config if that doesn't exist.
*/
const config& get_value_cfg(const std::string& id) const;
/**
* Returns the information about an option.
*
* @param id The id of the option.
*
* @return The config object which contains the
* settings of the option, or an empty config
* if the option was not found.
*/
const config& get_option_info_cfg(const std::string& id) const;
/**
* Finds the parent node of an options.
*
* @param id The id of the option.
*
* @return A config::any_child object containing the
* key and the data of the parent node, or ""
* for the key and an empty config if the
* option was not found.
*/
config::any_child get_option_parent(const std::string& id) const;
/**
* Retrieves the saved value for a certain option, or the default, if
* there's no such.
*
* @param id The id of the option.
*
* @return The value saved in values_ for this option
* or its specified default value if a saved
* value can't be found.
*/
config::attribute_value get_stored_value(const std::string& id) const;
/**
* Retrieves the default value for a certain option.
*
* @param id The id of the option.
*
* @return The default value for this option.
*/
config::attribute_value get_default_value(const std::string& id) const;
/**
* Gets the current value of a slider widget.
*
* @param id The id of the widget.
* @param win The window to find the widget in.
*
* @return The integer currently set on the slider.
*/
int get_slider_value(const std::string& id, gui2::twindow* win) const;
/**
* Gets the current value of a checkbox widget.
*
* @param id The id of the widget.
* @param win The window to find the widget in.
*
* @return True if the box is checked, false if not.
*/
bool get_checkbox_value(const std::string& id, gui2::twindow* win) const;
/**
* Gets the current value of a text_box widget.
*
* @param id The id of the widget.
* @param win The window to find the widget in.
*
* @return The text written in the widget.
*/
std::string get_entry_value(const std::string& id,
gui2::twindow* win) const;
void set_slider_value(int val, const std::string& id,
gui2::twindow* win) const;
void set_checkbox_value(bool val, const std::string& id,
gui2::twindow* win) const;
void set_entry_value(const std::string& val, const std::string& id,
gui2::twindow* win) const;
/**
* Writes all the values for the options of a certain component from a
* specified window into values_.
*
* @param key The component's type.
* @param id The component's id.
* @param window The window.
*/
void extract_values(const std::string& key, const std::string& id,
gui2::twindow* window);
/**
* Decides whether a config is a sane option node or not.
* A valid option node:
* - Must have an id field.
* - Its key must be "slider", "entry" or "checkbox"
*
* @param key The option's key.
* @param option The option's data.
*
* @return True if the option is valid, false if not.
*/
static bool is_valid_option(const std::string& key, const config& option);
/**
* Restores every widget's value to its default for a window.
*
* @param m A pointer to the manager which generated
* the window.
* @param w A pointer to the window itself.
*/
static void restore_defaults(manager* m, gui2::twindow* w);
/**
* Finds the widgets representing the options of a certain component in a
* window (era, scenario or modification) and sets their value to their
* defaults.
*
* @param comp The config of the component.
* @param m A pointer to the manager which generated
* the window.
* @param w A pointer to the window.
*/
static void restore_defaults_for_component(const config& comp, manager* m,
gui2::twindow* w);
/**
* @todo Implement a way to initialize the checkbox via WML and then
* remove this function altogether.
*
* Sets the default states for all checkbox widgets inside a window. All
* required data is fetched from values_ and options_info_.
*
* @param window The window.
*/
void __tmp_set_checkbox_defaults(gui2::twindow* window) const;
};
} // namespace options
} // namespace mp
#endif

View File

@ -36,6 +36,7 @@
#include "formula_string_utils.hpp"
#include "tod_manager.hpp"
#include "wml_exception.hpp"
#include "mp_options.hpp"
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
@ -1642,6 +1643,10 @@ void connect::load_game()
level_["turns"] = num_turns_;
level_.add_child("multiplayer", params_.to_config());
// Convert options to events
level_.add_child_at("event", mp::options::to_event(params_.options
.find_child("multiplayer", "id", params_.mp_scenario)), 0);
params_.hash = level_.hash();
level_["next_underlying_unit_id"] = 0;
n_unit::id_manager::instance().clear();
@ -1690,7 +1695,22 @@ void connect::load_game()
BOOST_FOREACH(const config &e, era_cfg.child_range("multiplayer_side")) {
era_sides_.push_back(&e);
}
level_.add_child("era", era_cfg);
config& cfg = level_.add_child("era", era_cfg);
// Convert options to event
cfg.add_child_at("event", mp::options::to_event
(params_.options.find_child("era", "id", era)), 0);
}
// Add modifications
const std::vector<std::string>& mods = params_.active_mods;
for (unsigned i = 0; i<mods.size(); i++) {
config& cfg = level_.add_child("modification",
game_config().find_child("modification", "id", mods[i]));
// Convert options to event
cfg.add_child_at("event", mp::options::to_event
(params_.options.find_child("modification", "id", mods[i])), 0);
}
gold_title_label_.hide(params_.saved_game);

View File

@ -29,6 +29,7 @@
#include "map_exception.hpp"
#include "map_create.hpp"
#include "gui/dialogs/message.hpp"
#include "gui/dialogs/mp_create_game_choose_mods.hpp"
#include "gui/dialogs/mp_create_game_set_password.hpp"
#include "gui/dialogs/transient_message.hpp"
#include "minimap.hpp"
@ -58,11 +59,13 @@ create::create(game_display& disp, const config &cfg, chat& c, config& gamelist,
local_players_only_(local_players_only),
tooltip_manager_(disp.video()),
era_selection_(-1),
map_selection_(-1),
mp_countdown_init_time_(270),
mp_countdown_reservoir_time_(330),
user_maps_(),
map_options_(),
available_mods_(),
map_index_(),
maps_menu_(disp.video(), std::vector<std::string>()),
@ -94,11 +97,13 @@ create::create(game_display& disp, const config &cfg, chat& c, config& gamelist,
shroud_game_(disp.video(), _("Shroud"), gui::button::TYPE_CHECK),
observers_game_(disp.video(), _("Observers"), gui::button::TYPE_CHECK),
shuffle_sides_(disp.video(), _("Shuffle sides"), gui::button::TYPE_CHECK),
options_(disp.video(), _("Options...")),
cancel_game_(disp.video(), _("Cancel")),
launch_game_(disp.video(), _("OK")),
regenerate_map_(disp.video(), _("Regenerate")),
generator_settings_(disp.video(), _("Settings...")),
password_button_(disp.video(), _("Set Password...")),
choose_mods_(disp.video(), _("Modifications...")),
era_combo_(disp, std::vector<std::string>()),
vision_combo_(disp, std::vector<std::string>()),
name_entry_(disp.video(), 32),
@ -106,7 +111,9 @@ create::create(game_display& disp, const config &cfg, chat& c, config& gamelist,
minimap_rect_(null_rect),
generator_(NULL),
num_turns_(0),
parameters_()
parameters_(),
dependency_manager_(cfg, disp.video()),
options_manager_(cfg, disp.video(), preferences::options())
{
// Build the list of scenarios to play
@ -127,6 +134,22 @@ create::create(game_display& disp, const config &cfg, chat& c, config& gamelist,
{
menu_help_str = help_sep + user_maps_[i];
map_options_.push_back(user_maps_[i] + menu_help_str);
// Since user maps are treated as scenarios,
// some dependency info is required
config depinfo;
depinfo["id"] = user_maps_[i];
depinfo["name"] = user_maps_[i];
dependency_manager_.insert_element(depcheck::SCENARIO, depinfo, i);
// Same with options
// FIXME: options::elem_type duplicates depcheck::component_type
// Perhaps they should me merged?
config optinfo = depinfo;
options_manager_.insert_element(options::SCENARIO, optinfo, i);
}
// Standard maps
@ -145,8 +168,11 @@ create::create(game_display& disp, const config &cfg, chat& c, config& gamelist,
// Create the scenarios menu
maps_menu_.set_items(map_options_);
if (size_t(preferences::map()) < map_options_.size())
if (size_t(preferences::map()) < map_options_.size()) {
maps_menu_.move_selection(preferences::map());
dependency_manager_.try_scenario_by_index(preferences::map(), true);
options_manager_.set_scenario_by_index(preferences::map());
}
maps_menu_.set_numeric_keypress_selection(false);
turns_slider_.set_min(settings::turns_min);
@ -238,9 +264,25 @@ create::create(game_display& disp, const config &cfg, chat& c, config& gamelist,
if (size_t(preferences::era()) < eras.size()) {
era_combo_.set_selected(preferences::era());
} else {
era_combo_.set_selected(0);
era_combo_.set_selected(preferences::era());
}
dependency_manager_.try_era_by_index(era_selection_, true);
options_manager_.set_era_by_index(era_selection_);
// Available modifications
BOOST_FOREACH (const config& mod, cfg.child_range("modification")) {
available_mods_.add_child("modification", mod);
}
BOOST_FOREACH (const std::string& str, preferences::modifications()) {
if (cfg.find_child("modification", "id", str))
parameters_.active_mods.push_back(str);
}
dependency_manager_.try_modifications(parameters_.active_mods, true);
options_manager_.set_modifications(parameters_.active_mods);
utils::string_map i18n_symbols;
i18n_symbols["login"] = preferences::login();
@ -271,8 +313,10 @@ create::~create()
preferences::set_countdown_turn_bonus(parameters_.mp_countdown_turn_bonus);
preferences::set_countdown_reservoir_time(parameters_.mp_countdown_reservoir_time);
preferences::set_countdown_action_bonus(parameters_.mp_countdown_action_bonus);
preferences::set_era(era_combo_.selected()); /** @todo FIXME: may be broken if new eras are added. */
preferences::set_era(era_selection_); /** @todo FIXME: may be broken if new eras are added. */
preferences::set_map(map_selection_);
preferences::set_modifications(parameters_.active_mods);
preferences::set_options(parameters_.options);
// When using map settings, the following variables are determined by the map,
// so don't store them as the new preferences.
@ -334,6 +378,7 @@ mp_game_settings& create::get_parameters()
parameters_.shuffle_sides = shuffle_sides_.checked();
parameters_.share_view = vision_combo_.selected() == 0;
parameters_.share_maps = vision_combo_.selected() == 1;
parameters_.options = options_manager_.get_values();
return parameters_;
}
@ -368,12 +413,35 @@ void create::process_event()
}
}
if(options_.pressed()) {
options_manager_.show_dialog();
}
if(password_button_.pressed()) {
gui2::tmp_create_game_set_password::execute(
parameters_.password
, disp_.video());
}
if(choose_mods_.pressed()) {
if (available_mods_.empty()) {
gui2::show_transient_message(disp_.video(), "",
_( "There are no modifications currently installed." \
" To download modifications, connect to the add-ons server" \
" by choosing the 'Add-ons' option on the main screen." ));
} else {
gui2::tmp_create_game_choose_mods
dialog(available_mods_, parameters_.active_mods);
dialog.show(disp_.video());
dependency_manager_.try_modifications(parameters_.active_mods);
options_manager_.set_modifications(parameters_.active_mods);
synchronize_selections();
}
}
// Turns per game
const int cur_turns = turns_slider_.value();
@ -446,9 +514,24 @@ void create::process_event()
xp_modifier_label_.set_text(buf.str());
bool era_changed = era_selection_ != era_combo_.selected();
era_selection_ = era_combo_.selected();
if (era_changed) {
dependency_manager_.try_era_by_index(era_selection_);
options_manager_.set_era_by_index(era_selection_);
synchronize_selections();
}
bool map_changed = map_selection_ != maps_menu_.selection();
map_selection_ = maps_menu_.selection();
if (map_changed) {
dependency_manager_.try_scenario_by_index(map_selection_);
options_manager_.set_scenario_by_index(map_selection_);
synchronize_selections();
}
if(map_changed) {
generator_.assign(NULL);
@ -757,6 +840,8 @@ void create::layout_children(const SDL_Rect& rect)
ypos += era_label_.height() + border_size;
era_combo_.set_location(xpos, ypos);
ypos += era_combo_.height() + border_size;
choose_mods_.set_location(xpos, ypos);
ypos += choose_mods_.height() + border_size;
if(!local_players_only_) {
password_button_.set_location(xpos, ypos);
ypos += password_button_.height() + border_size;
@ -857,6 +942,28 @@ void create::layout_children(const SDL_Rect& rect)
ca.y + ca.h - right_button->height());
left_button->set_location(right_button->location().x - left_button->width() -
gui::ButtonHPadding, ca.y + ca.h - left_button->height());
options_.set_location(left_button->location().x - options_.width() -
gui::ButtonHPadding, ca.y + ca.h - options_.height());
}
void create::synchronize_selections()
{
DBG_MP << "Synchronizing with the dependency manager" << std::endl;
if (era_selection_ != dependency_manager_.get_era_index()) {
era_combo_.set_selected(dependency_manager_.get_era_index());
process_event();
}
if (map_selection_ != dependency_manager_.get_scenario_index()) {
maps_menu_.move_selection(dependency_manager_.get_scenario_index());
process_event();
}
parameters_.active_mods = dependency_manager_.get_modifications();
options_manager_.set_modifications(dependency_manager_.get_modifications());
options_manager_.set_era(dependency_manager_.get_era());
options_manager_.set_scenario(dependency_manager_.get_scenario());
}
} // namespace mp

View File

@ -18,7 +18,9 @@
#ifndef MULTIPLAYER_CREATE_HPP_INCLUDED
#define MULTIPLAYER_CREATE_HPP_INCLUDED
#include "mp_depcheck.hpp"
#include "mp_game_settings.hpp"
#include "mp_options.hpp"
#include "multiplayer_ui.hpp"
#include "widgets/slider.hpp"
#include "widgets/combo.hpp"
@ -43,9 +45,12 @@ protected:
private:
void synchronize_selections();
bool local_players_only_;
tooltips::manager tooltip_manager_;
int era_selection_;
int map_selection_;
int mp_countdown_init_time_;
int mp_countdown_reservoir_time_;
@ -53,6 +58,7 @@ private:
std::vector<std::string> user_maps_;
std::vector<std::string> map_options_;
config available_mods_;
/**
* Due to maps not available the index of the selected map and mp scenarios
@ -91,11 +97,13 @@ private:
gui::button shroud_game_;
gui::button observers_game_;
gui::button shuffle_sides_;
gui::button options_;
gui::button cancel_game_;
gui::button launch_game_;
gui::button regenerate_map_;
gui::button generator_settings_;
gui::button password_button_;
gui::button choose_mods_;
gui::combo era_combo_;
gui::combo vision_combo_;
@ -108,6 +116,9 @@ private:
int num_turns_;
mp_game_settings parameters_;
depcheck::manager dependency_manager_;
options::manager options_manager_;
};
} // end namespace mp

View File

@ -159,6 +159,9 @@ void gamebrowser::draw_row(const size_t index, const SDL_Rect& item_rect, ROW_TY
font_color = font::DISABLED_COLOR;
no_era_string = _(" (Unknown Era)");
}
if(!game.have_all_mods && font_color != font::BAD_COLOR) {
font_color = font::DISABLED_COLOR;
}
const surface status_text(font::get_rendered_text(game.status,
font::SIZE_NORMAL, font_color));
@ -179,7 +182,7 @@ void gamebrowser::draw_row(const size_t index, const SDL_Rect& item_rect, ROW_TY
}
// Second line
ypos = item_rect.y + item_rect.h/2;
ypos = item_rect.y + item_rect.h/3 + margin_;
// Draw map info
const surface map_info_surf(font::get_rendered_text(
@ -191,6 +194,18 @@ void gamebrowser::draw_row(const size_t index, const SDL_Rect& item_rect, ROW_TY
}
// Third line
ypos = item_rect.y + 2*item_rect.h/3 - margin_;
// Draw modifications info
const surface mod_info_surf(font::get_rendered_text(
font::make_text_ellipsis(game.mod_info, font::SIZE_NORMAL,
(item_rect.x + item_rect.w) - xpos - margin_),
font::SIZE_NORMAL, font::NORMAL_COLOR));
if(mod_info_surf) {
video().blit_surface(xpos, ypos - mod_info_surf->h/2, mod_info_surf);
}
// Fourth line
ypos = item_rect.y + item_rect.h - margin_;
// Draw observer icon
@ -525,6 +540,32 @@ void gamebrowser::set_game_items(const config& cfg, const config& game_config)
games_.back().map_info += _("Unknown scenario");
verified = false;
}
games_.back().mod_info += "Modifications: ";
if (!game.child_or_empty("modification").empty()) {
games_.back().have_all_mods = true;
BOOST_FOREACH (const config& m, game.child_range("modification")) {
const config& mod_cfg = game_config.find_child("modification", "id", m["id"]);
if (mod_cfg) {
games_.back().mod_info += mod_cfg["name"].str();
games_.back().mod_info += ", ";
} else {
games_.back().mod_info += m["id"].str();
if (m["require_modification"].to_bool(false)) {
games_.back().have_all_mods = false;
games_.back().mod_info += _(" (missing)");
}
games_.back().mod_info += ", ";
}
}
games_.back().mod_info.erase(games_.back().mod_info.size()-2, 2);
} else {
games_.back().mod_info += "none";
games_.back().have_all_mods = true;
}
if (games_.back().reloaded) {
games_.back().map_info += "";
games_.back().map_info += _("Reloaded game");

View File

@ -44,6 +44,7 @@ public:
name(),
map_info(),
map_info_size(),
mod_info(),
gold(),
xp(),
vision(),
@ -60,7 +61,8 @@ public:
use_map_settings(false),
verified(false),
password_required(false),
have_era(false)
have_era(false),
have_all_mods(false)
{
}
@ -70,6 +72,7 @@ public:
std::string name;
std::string map_info;
std::string map_info_size;
std::string mod_info;
std::string gold;
std::string xp;
std::string vision;
@ -87,6 +90,7 @@ public:
bool verified;
bool password_required;
bool have_era;
bool have_all_mods;
};
gamebrowser(CVideo& video, const config &map_hashes);
void scroll(unsigned int pos);
@ -100,9 +104,9 @@ public:
SDL_Rect get_item_rect(size_t index) const;
bool empty() const { return games_.empty(); }
bool selection_is_joinable() const
{ return empty() ? false : (games_[selected_].vacant_slots > 0 && !games_[selected_].started && games_[selected_].have_era); }
{ return empty() ? false : (games_[selected_].vacant_slots > 0 && !games_[selected_].started && games_[selected_].have_era && games_[selected_].have_all_mods); }
// Moderators may observe any game.
bool selection_is_observable() const { return empty() ? false : (games_[selected_].observers && games_[selected_].have_era) || preferences::is_authenticated(); }
bool selection_is_observable() const { return empty() ? false : (games_[selected_].observers && games_[selected_].have_era && games_[selected_].have_all_mods) || preferences::is_authenticated(); }
bool selected() const { return double_clicked_ && !empty(); }
void reset_selection() { double_clicked_ = false; }
int selection() const { return selected_; }

View File

@ -282,11 +282,17 @@ void play_controller::init(CVideo& video){
browse_ = true;
init_managers();
// add era events for MP game
// add era & mod events for MP game
if (const config &era_cfg = level_.child("era")) {
game_events::add_events(era_cfg.child_range("event"), "era_events");
}
if (level_.child_or_empty("modification")) {
BOOST_FOREACH (const config& mod_cfg, level_.child_range("modification")) {
game_events::add_events(mod_cfg.child_range("event"),
"mod_" + mod_cfg["id"].str() + "_events");
}
}
loadscreen::global_loadscreen->start_stage("start game");
loadscreen_manager->reset();
}

View File

@ -4026,9 +4026,9 @@ void LuaKernel::initialize()
static char const *handled_file_tags[] = {
"color_palette", "color_range", "era", "event", "generator",
"label", "lua", "map", "menu_item", "music", "side", "sound_source", "story",
"terrain_graphics", "time", "time_area", "tunnel", "variables", "endlevel",
"display",
"label", "lua", "map", "menu_item", "modification", "music", "options",
"side", "sound_source", "story","terrain_graphics", "time", "time_area",
"tunnel", "variables", "endlevel", "display",
//TODO: These are only needed for MP campaigns and only for subsequent scenarios, see bug #18883
"snapshot", "multiplayer", "replay_start"
};

View File

@ -2404,6 +2404,7 @@ void server::process_data_game(const network::connection sock,
rooms_.lobby().send_server_message("The scenario data is missing the [multiplayer] tag which contains the game settings. Game aborted.", sock);
return;
}
g->set_description(&desc);
desc.set_attr_dup("id", lexical_cast<std::string>(g->id()).c_str());
} else {
@ -2428,6 +2429,14 @@ void server::process_data_game(const network::connection sock,
}
}
const simple_wml::node::child_list& mlist = data.children("modification");
BOOST_FOREACH (const simple_wml::node* m, mlist) {
desc.add_child_at("modification", 0);
desc.child("modification")->set_attr_dup("id", m->attr("id"));
if (m->attr("require_modification").to_bool(false))
desc.child("modification")->set_attr("require_modification", "yes");
}
// Record the full scenario in g->level()
g->level().swap(data);
// The host already put himself in the scenario so we just need
@ -2497,6 +2506,7 @@ void server::process_data_game(const network::connection sock,
rooms_.lobby().send_server_message("The scenario data is missing the [multiplayer] tag which contains the game settings. Game aborted.", sock);
return;
}
// If there is no shroud, then tell players in the lobby
// what the map looks like.
const simple_wml::node& s = g->level().root();

View File

@ -242,6 +242,10 @@ public:
return root().child(name);
}
const node::child_list& children(const char* name) const {
return root().children(name);
}
node& set_attr(const char* key, const char* value) {
return root().set_attr(key, value);
}