← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/economy-target-profiles into lp:widelands

 

Benedikt Straub has proposed merging lp:~widelands-dev/widelands/economy-target-profiles into lp:widelands.

Commit message:
Users can define and save their own profiles of economy target quantities. Redesigned the economy options menu.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1827696 in widelands: "Allow users to define their own economy default settings"
  https://bugs.launchpad.net/widelands/+bug/1827696

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/economy-target-profiles/+merge/366952

For each tribe, I added two profiles: "Efficiency" is equal to the changes proposed previously, and "Stockpile" is for people who, well, like to stockpile stuff. Additionally there is an unchangeable "Default" profile that resets stuff to the default settings.
To apply a profile, select the profile and the wares you wish to change and click Apply (replaces Reset). Use "Save" to save your current settings as a profile. The save window also allows you to delete profiles.
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/economy-target-profiles into lp:widelands.
=== added directory 'data/economy_profiles'
=== added file 'data/economy_profiles/atlanteans'
--- data/economy_profiles/atlanteans	1970-01-01 00:00:00 +0000
+++ data/economy_profiles/atlanteans	2019-05-04 19:31:09 +0000
@@ -0,0 +1,85 @@
+# Automatically created by Widelands bzr9094[economy-target-profiles] (Debug)
+
+[Efficiency]
+blackroot_flour="1"
+atlanteans_bread="20"
+bread_paddle="0"
+buckets="0"
+coal="5"
+cornmeal="3"
+diamond="3"
+fire_tongs="1"
+fishing_net="2"
+gold="1"
+gold_ore="1"
+gold_thread="0"
+granite="10"
+hammer="0"
+hook_pole="0"
+hunting_bow="1"
+iron="5"
+iron_ore="1"
+milking_tongs="0"
+pick="1"
+planks="1"
+quartz="3"
+saw="0"
+scythe="0"
+shield_advanced="0"
+shield_steel="0"
+shovel="0"
+smoked_fish="5"
+smoked_meat="3"
+spidercloth="5"
+spider_silk="5"
+tabard="1"
+tabard_golden="0"
+trident_double="0"
+trident_heavy_double="0"
+trident_light="1"
+trident_long="0"
+trident_steel="0"
+atlanteans_horse="1"
+atlanteans_soldier="10"
+
+[Stockpile]
+blackroot_flour="20"
+atlanteans_bread="30"
+bread_paddle="1"
+buckets="2"
+coal="25"
+cornmeal="20"
+diamond="10"
+fire_tongs="1"
+fishing_net="2"
+gold="20"
+gold_ore="15"
+gold_thread="5"
+granite="30"
+hammer="2"
+hook_pole="1"
+hunting_bow="1"
+iron="25"
+iron_ore="20"
+milking_tongs="1"
+pick="3"
+planks="40"
+quartz="10"
+saw="2"
+scythe="1"
+shield_advanced="1"
+shield_steel="1"
+shovel="2"
+smoked_fish="40"
+smoked_meat="25"
+spidercloth="20"
+spider_silk="15"
+tabard="30"
+tabard_golden="1"
+trident_double="1"
+trident_heavy_double="1"
+trident_light="30"
+trident_long="1"
+trident_steel="1"
+atlanteans_horse="20"
+atlanteans_soldier="20"

=== added file 'data/economy_profiles/barbarians'
--- data/economy_profiles/barbarians	1970-01-01 00:00:00 +0000
+++ data/economy_profiles/barbarians	2019-05-04 19:31:09 +0000
@@ -0,0 +1,77 @@
+# Automatically created by Widelands bzr9094[economy-target-profiles] (Debug)
+
+[Efficiency]
+ax="1"
+ax_battle="0"
+ax_broad="0"
+ax_bronze="0"
+ax_sharp="0"
+ax_warriors="0"
+beer="0"
+beer_strong="1"
+blackwood="40"
+barbarians_bread="5"
+bread_paddle="0"
+cloth="10"
+coal="20"
+felling_ax="0"
+fire_tongs="1"
+fishing_rod="0"
+gold="1"
+gold_ore="1"
+granite="10"
+grout="1"
+hammer="1"
+helmet="0"
+helmet_mask="0"
+helmet_warhelm="0"
+hunting_spear="0"
+iron="5"
+iron_ore="5"
+kitchen_tools="0"
+meal="5"
+pick="1"
+ration="20"
+scythe="0"
+shovel="0"
+snack="0"
+barbarians_ox="1"
+barbarians_soldier="10"
+
+[Stockpile]
+ax="30"
+ax_battle="1"
+ax_broad="1"
+ax_bronze="1"
+ax_sharp="1"
+ax_warriors="1"
+beer="15"
+beer_strong="20"
+blackwood="45"
+barbarians_bread="25"
+bread_paddle="1"
+cloth="10"
+coal="25"
+felling_ax="5"
+fire_tongs="1"
+fishing_rod="1"
+gold="20"
+gold_ore="15"
+granite="30"
+grout="20"
+hammer="2"
+helmet="1"
+helmet_mask="1"
+helmet_warhelm="1"
+hunting_spear="1"
+iron="25"
+iron_ore="20"
+kitchen_tools="1"
+meal="15"
+pick="2"
+ration="30"
+scythe="1"
+shovel="1"
+snack="20"
+barbarians_ox="20"
+barbarians_soldier="20"

=== added file 'data/economy_profiles/empire'
--- data/economy_profiles/empire	1970-01-01 00:00:00 +0000
+++ data/economy_profiles/empire	2019-05-04 19:31:09 +0000
@@ -0,0 +1,85 @@
+# Automatically created by Widelands bzr9094[economy-target-profiles] (Debug)
+
+[Efficiency]
+armor="1"
+armor_chain="1"
+armor_gilded="1"
+armor_helmet="30"
+basket="1"
+beer="1"
+empire_bread="20"
+bread_paddle="0"
+cloth="15"
+coal="5"
+felling_ax="0"
+fire_tongs="1"
+fishing_rod="0"
+flour="20"
+gold="1"
+gold_ore="1"
+granite="10"
+hammer="0"
+hunting_spear="0"
+iron="5"
+iron_ore="3"
+kitchen_tools="0"
+marble="30"
+marble_column="10"
+meal="5"
+meat="20"
+pick="1"
+planks="1"
+ration="20"
+saw="0"
+scythe="0"
+shovel="0"
+spear="1"
+spear_advanced="1"
+spear_heavy="1"
+spear_war="1"
+spear_wooden="30"
+wool="10"
+empire_donkey="1"
+empire_soldier="10"
+
+[Stockpile]
+armor="1"
+armor_chain="1"
+armor_gilded="1"
+armor_helmet="30"
+basket="1"
+beer="20"
+empire_bread="30"
+bread_paddle="1"
+cloth="15"
+coal="25"
+felling_ax="3"
+fire_tongs="1"
+fishing_rod="1"
+flour="25"
+gold="20"
+gold_ore="15"
+granite="30"
+hammer="2"
+hunting_spear="1"
+iron="25"
+iron_ore="20"
+kitchen_tools="1"
+marble="35"
+marble_column="15"
+meal="20"
+meat="30"
+pick="2"
+planks="40"
+ration="25"
+saw="1"
+scythe="1"
+shovel="1"
+spear="1"
+spear_advanced="1"
+spear_heavy="1"
+spear_war="1"
+spear_wooden="30"
+wool="15"
+empire_donkey="20"
+empire_soldier="20"

=== added file 'data/economy_profiles/frisians'
--- data/economy_profiles/frisians	1970-01-01 00:00:00 +0000
+++ data/economy_profiles/frisians	2019-05-04 19:31:09 +0000
@@ -0,0 +1,89 @@
+# Automatically created by Widelands bzr9093[economy-target-profiles] (Debug)
+
+[Efficiency]
+clay="30"
+brick="40"
+bread_frisians="20"
+honey_bread="20"
+mead="15"
+fur="10"
+fur_garment="30"
+fur_garment_studded="2"
+fur_garment_golden="2"
+helmet_golden="2"
+sword_short="30"
+sword_long="2"
+sword_broad="2"
+sword_double="2"
+needles="1"
+basket="1"
+beer="1"
+bread_paddle="1"
+cloth="10"
+coal="20"
+felling_ax="0"
+fire_tongs="1"
+fish="20"
+fishing_net="2"
+gold="1"
+gold_ore="1"
+granite="10"
+hammer="1"
+helmet="0"
+hunting_spear="0"
+iron="5"
+iron_ore="3"
+kitchen_tools="0"
+meal="1"
+pick="1"
+ration="20"
+scythe="0"
+shovel="0"
+smoked_fish="20"
+smoked_meat="10"
+frisians_reindeer="1"
+frisians_soldier="10"
+
+[Stockpile]
+clay="35"
+brick="50"
+bread_frisians="30"
+honey_bread="30"
+mead="30"
+fur="20"
+fur_garment="30"
+fur_garment_studded="2"
+fur_garment_golden="2"
+helmet_golden="2"
+sword_short="30"
+sword_long="2"
+sword_broad="2"
+sword_double="2"
+needles="1"
+basket="1"
+beer="30"
+bread_paddle="1"
+cloth="10"
+coal="35"
+felling_ax="3"
+fire_tongs="2"
+fish="40"
+fishing_net="2"
+gold="20"
+gold_ore="15"
+granite="35"
+hammer="3"
+helmet="2"
+hunting_spear="1"
+iron="25"
+iron_ore="20"
+kitchen_tools="2"
+meal="10"
+pick="3"
+ration="30"
+scythe="2"
+shovel="5"
+smoked_fish="30"
+smoked_meat="20"
+frisians_reindeer="20"
+frisians_soldier="20"

=== modified file 'src/logic/filesystem_constants.h'
--- src/logic/filesystem_constants.h	2019-04-18 16:50:35 +0000
+++ src/logic/filesystem_constants.h	2019-05-04 19:31:09 +0000
@@ -78,4 +78,6 @@
 /// Filesystem names for config
 const std::string kConfigFile = "config";
 
+const std::string kEconomyProfilesDir = "economy_profiles";
+
 #endif  // end of include guard: WL_LOGIC_FILESYSTEM_CONSTANTS_H

=== modified file 'src/logic/playercommand.h'
--- src/logic/playercommand.h	2019-02-23 11:00:49 +0000
+++ src/logic/playercommand.h	2019-05-04 19:31:09 +0000
@@ -581,6 +581,7 @@
 	uint32_t permanent_;
 };
 
+// TODO(Nordfriese): CmdResetWareTargetQuantity can be removed when we next break savegame compatibility
 struct CmdResetWareTargetQuantity : public CmdChangeTargetQuantity {
 	CmdResetWareTargetQuantity() : CmdChangeTargetQuantity() {
 	}
@@ -629,6 +630,7 @@
 	uint32_t permanent_;
 };
 
+// TODO(Nordfriese): CmdResetWorkerTargetQuantity can be removed when we next break savegame compatibility
 struct CmdResetWorkerTargetQuantity : public CmdChangeTargetQuantity {
 	CmdResetWorkerTargetQuantity() : CmdChangeTargetQuantity() {
 	}

=== modified file 'src/wui/economy_options_window.cc'
--- src/wui/economy_options_window.cc	2019-02-23 11:00:49 +0000
+++ src/wui/economy_options_window.cc	2019-05-04 19:31:09 +0000
@@ -19,15 +19,22 @@
 
 #include "wui/economy_options_window.h"
 
+#include <memory>
+
 #include <boost/lexical_cast.hpp>
 
 #include "graphic/graphic.h"
 #include "logic/editor_game_base.h"
+#include "logic/filesystem_constants.h"
 #include "logic/map_objects/tribes/ware_descr.h"
 #include "logic/map_objects/tribes/worker_descr.h"
 #include "logic/player.h"
 #include "logic/playercommand.h"
+#include "profile/profile.h"
 #include "ui_basic/button.h"
+#include "ui_basic/editbox.h"
+#include "ui_basic/messagebox.h"
+#include "ui_basic/table.h"
 
 static const char pic_tab_wares[] = "images/wui/buildings/menu_tab_wares.png";
 static const char pic_tab_workers[] = "images/wui/buildings/menu_tab_workers.png";
@@ -36,19 +43,73 @@
                                            Widelands::Economy* economy,
                                            bool can_act)
    : UI::Window(parent, "economy_options", 0, 0, 0, 0, _("Economy options")),
+     main_box_(this, 0, 0, UI::Box::Vertical),
      serial_(economy->serial()),
      player_(&economy->owner()),
      tabpanel_(this, UI::TabPanelStyle::kWuiDark),
-     ware_panel_(new EconomyOptionsPanel(&tabpanel_, serial_, player_, can_act, Widelands::wwWARE)),
+     ware_panel_(new EconomyOptionsPanel(&tabpanel_, this, serial_, player_, can_act, Widelands::wwWARE)),
      worker_panel_(
-        new EconomyOptionsPanel(&tabpanel_, serial_, player_, can_act, Widelands::wwWORKER)) {
-	set_center_panel(&tabpanel_);
+        new EconomyOptionsPanel(&tabpanel_, this, serial_, player_, can_act, Widelands::wwWORKER)),
+     dropdown_box_(this, 0, 0, UI::Box::Horizontal),
+     dropdown_(&dropdown_box_, 0, 0, 140, 200, 34, "", UI::DropdownType::kTextual, UI::PanelStyle::kWui) {
+	set_center_panel(&main_box_);
 
 	tabpanel_.add("wares", g_gr->images().get(pic_tab_wares), ware_panel_, _("Wares"));
 	tabpanel_.add("workers", g_gr->images().get(pic_tab_workers), worker_panel_, _("Workers"));
+
+	UI::Box* buttons = new UI::Box(this, 0, 0, UI::Box::Horizontal);
+	UI::Button* b = new UI::Button(buttons, "decrease_target_fast", 0, 0, 34, 34,
+	                               UI::ButtonStyle::kWuiMenu, "--", _("Decrease target by 10"));
+	b->sigclicked.connect(boost::bind(&EconomyOptionsWindow::change_target, this, -10));
+	buttons->add(b);
+	b->set_repeating(true);
+	buttons->add_space(8);
+	b = new UI::Button(buttons, "decrease_target", 0, 0, 34, 34,
+	                               UI::ButtonStyle::kWuiMenu, "-", _("Decrease target"));
+	b->sigclicked.connect(boost::bind(&EconomyOptionsWindow::change_target, this, -1));
+	buttons->add(b);
+	b->set_repeating(true);
+	buttons->add_space(34);
+
+	b = new UI::Button(buttons, "increase_target", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, "+",
+	                   _("Increase target"));
+	b->sigclicked.connect(boost::bind(&EconomyOptionsWindow::change_target, this, 1));
+	buttons->add(b);
+	b->set_repeating(true);
+	buttons->add_space(8);
+	b = new UI::Button(buttons, "increase_target_fast", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, "++",
+	                   _("Increase target by 10"));
+	b->sigclicked.connect(boost::bind(&EconomyOptionsWindow::change_target, this, 10));
+	buttons->add(b);
+	b->set_repeating(true);
+
+	dropdown_.set_tooltip(_("Profile to apply"));
+	dropdown_box_.set_size(40, 20);  // Prevent assert failures
+	dropdown_box_.add(&dropdown_, UI::Box::Resizing::kFullSize);
+
+	b = new UI::Button(&dropdown_box_, "apply_defaults", 0, 0, 60, 34,
+			UI::ButtonStyle::kWuiMenu, _("Apply"), _("Use the chosen profile for the selected items"));
+	b->sigclicked.connect(boost::bind(&EconomyOptionsWindow::reset_target, this));
+	dropdown_box_.add_space(8);
+	dropdown_box_.add(b);
+
+	b = new UI::Button(&dropdown_box_, "save_targets", 0, 0, 60, 34,
+			UI::ButtonStyle::kWuiMenu, _("Save…"), _("Save target settings"));
+	b->sigclicked.connect(boost::bind(&EconomyOptionsWindow::create_target, this));
+	dropdown_box_.add_space(8);
+	dropdown_box_.add(b);
+
+	main_box_.add(&tabpanel_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+	main_box_.add_space(8);
+	main_box_.add(buttons, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+	main_box_.add_space(8);
+	main_box_.add(&dropdown_box_, UI::Box::Resizing::kAlign, UI::Align::kCenter);
+
 	economy->set_has_window(true);
 	economynotes_subscriber_ = Notifications::subscribe<Widelands::NoteEconomy>(
 	   [this](const Widelands::NoteEconomy& note) { on_economy_note(note); });
+
+	read_targets();
 }
 
 EconomyOptionsWindow::~EconomyOptionsWindow() {
@@ -129,6 +190,7 @@
  * Wraps the wares/workers display together with some buttons
  */
 EconomyOptionsWindow::EconomyOptionsPanel::EconomyOptionsPanel(UI::Panel* parent,
+		                    								   EconomyOptionsWindow* eco_window,
                                                                Widelands::Serial serial,
                                                                Widelands::Player* player,
                                                                bool can_act,
@@ -138,33 +200,13 @@
      player_(player),
      type_(type),
      can_act_(can_act),
-     display_(this, 0, 0, serial_, player_, type_, can_act_) {
+     display_(this, 0, 0, serial_, player_, type_, can_act_),
+     economy_options_window_(eco_window) {
 	add(&display_, UI::Box::Resizing::kFullSize);
 
 	if (!can_act_) {
 		return;
 	}
-	UI::Box* buttons = new UI::Box(this, 0, 0, UI::Box::Horizontal);
-	add(buttons);
-
-	UI::Button* b = new UI::Button(buttons, "decrease_target", 0, 0, 34, 34,
-	                               UI::ButtonStyle::kWuiMenu, "-", _("Decrease target"));
-	b->sigclicked.connect(boost::bind(&EconomyOptionsPanel::change_target, this, -1));
-	buttons->add(b);
-	b->set_repeating(true);
-	buttons->add_space(8);
-
-	b = new UI::Button(buttons, "increase_target", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, "+",
-	                   _("Increase target"));
-	b->sigclicked.connect(boost::bind(&EconomyOptionsPanel::change_target, this, 1));
-	buttons->add(b);
-	b->set_repeating(true);
-	buttons->add_space(8);
-
-	b = new UI::Button(
-	   buttons, "reset_target", 0, 0, 34, 34, UI::ButtonStyle::kWuiMenu, "R", _("Reset to default"));
-	b->sigclicked.connect(boost::bind(&EconomyOptionsPanel::reset_target, this));
-	buttons->add(b);
 }
 
 void EconomyOptionsWindow::EconomyOptionsPanel::set_economy(Widelands::Serial serial) {
@@ -172,7 +214,26 @@
 	display_.set_economy(serial);
 }
 
-void EconomyOptionsWindow::EconomyOptionsPanel::change_target(int amount) {
+void EconomyOptionsWindow::change_target(int amount) {
+	if (tabpanel_.active() == 0) {
+		ware_panel_->change_target(amount);
+	} else {
+		worker_panel_->change_target(amount);
+	}
+}
+
+void EconomyOptionsWindow::reset_target() {
+	if (tabpanel_.active() == 0) {
+		ware_panel_->reset_target();
+	} else {
+		worker_panel_->reset_target();
+	}
+}
+
+void EconomyOptionsWindow::EconomyOptionsPanel::change_target(int delta) {
+	if (delta == 0) {
+		return;
+	}
 	Widelands::Economy* economy = player_->get_economy(serial_);
 	if (economy == nullptr) {
 		die();
@@ -186,35 +247,264 @@
 			const Widelands::Economy::TargetQuantity& tq = is_wares ?
 			                                                  economy->ware_target_quantity(index) :
 			                                                  economy->worker_target_quantity(index);
-			// Don't allow negative new amount.
-			if (amount >= 0 || -amount <= static_cast<int>(tq.permanent)) {
-				if (is_wares) {
-					game.send_player_command(*new Widelands::CmdSetWareTargetQuantity(
-					   game.get_gametime(), player_->player_number(), serial_, index,
-					   tq.permanent + amount));
-				} else {
-					game.send_player_command(*new Widelands::CmdSetWorkerTargetQuantity(
-					   game.get_gametime(), player_->player_number(), serial_, index,
-					   tq.permanent + amount));
+			// Don't allow negative new amount
+			const int old_amount = static_cast<int>(tq.permanent);
+			const int new_amount = std::max(0, old_amount + delta);
+			if (new_amount == old_amount) {
+				continue;
+			}
+			if (is_wares) {
+				game.send_player_command(*new Widelands::CmdSetWareTargetQuantity(
+				   game.get_gametime(), player_->player_number(), serial_, index, new_amount));
+			} else {
+				game.send_player_command(*new Widelands::CmdSetWorkerTargetQuantity(
+				   game.get_gametime(), player_->player_number(), serial_, index, new_amount));
+			}
+		}
+	}
+}
+
+void EconomyOptionsWindow::EconomyOptionsPanel::reset_target() {
+	Widelands::Game& game = dynamic_cast<Widelands::Game&>(player_->egbase());
+	const bool is_wares = type_ == Widelands::wwWARE;
+	const auto& items = is_wares ? player_->tribe().wares() : player_->tribe().workers();
+	const PredefinedTargets settings = economy_options_window_->get_selected_target();
+	for (const Widelands::DescriptionIndex& index : items) {
+		if (display_.ware_selected(index)) {
+			if (is_wares) {
+				game.send_player_command(*new Widelands::CmdSetWareTargetQuantity(
+						game.get_gametime(), player_->player_number(), serial_, index, settings.wares.at(index)));
+			} else {
+				game.send_player_command(*new Widelands::CmdSetWorkerTargetQuantity(
+						game.get_gametime(), player_->player_number(), serial_, index, settings.workers.at(index)));
+			}
+		}
+	}
+}
+
+void EconomyOptionsWindow::read_targets(const std::string& select) {
+	predefined_targets_.clear();
+	const Widelands::Tribes& tribes = player_->egbase().tribes();
+	const Widelands::TribeDescr& tribe = player_->tribe();
+
+	{
+		PredefinedTargets t;
+		for (Widelands::DescriptionIndex di : tribe.wares()) {
+			const Widelands::WareDescr* descr = tribes.get_ware_descr(di);
+			if (descr->has_demand_check(tribe.name())) {
+				t.wares.emplace(di, descr->default_target_quantity(tribe.name()));
+			}
+		}
+		for (Widelands::DescriptionIndex di : tribe.workers()) {
+			const Widelands::WorkerDescr* descr = tribes.get_worker_descr(di);
+			if (descr->has_demand_check()) {
+				t.workers.emplace(di, descr->default_target_quantity());
+			}
+		}
+		predefined_targets_.emplace(kDefaultEconomyProfile, t);
+	}
+
+	std::string complete_filename = kEconomyProfilesDir + g_fs->file_separator() + player_->tribe().name();
+	Profile profile;
+	profile.read(complete_filename.c_str());
+	while (Section* section = profile.get_next_section()) {
+		PredefinedTargets t;
+		while (Section::Value* v = section->get_next_val()) {
+			const std::string name = std::string(v->get_name());
+			Widelands::DescriptionIndex di = tribes.ware_index(name);
+			if (di == Widelands::INVALID_INDEX) {
+				di = tribes.worker_index(name);
+				assert(di != Widelands::INVALID_INDEX);
+				t.workers.emplace(di, v->get_natural());
+			} else {
+				t.wares.emplace(di, v->get_natural());
+			}
+		}
+		predefined_targets_.emplace(std::string(section->get_name()), t);
+	}
+
+	update_profiles(select);
+}
+
+void EconomyOptionsWindow::update_profiles(const std::string& select) {
+	dropdown_.clear();
+	for (const auto& pair : predefined_targets_) {
+		dropdown_.add(_(pair.first), pair.first, nullptr, pair.first == select);
+	}
+}
+
+struct SaveProfileWindow : public UI::Window {
+	void update_save_enabled() {
+		const std::string& text = profile_name_.text();
+		if (text.empty() || text == kDefaultEconomyProfile) {
+			save_.set_enabled(false);
+			save_.set_tooltip(text.empty() ? _("The profile name cannot be empty") :
+					_("The default profile cannot be overwritten"));
+		} else {
+			save_.set_enabled(true);
+			save_.set_tooltip(_("Save the profile under this name"));
+		}
+	}
+
+	void table_selection_changed() {
+		if (!table_.has_selection()) {
+			delete_.set_enabled(false);
+			delete_.set_tooltip("");
+			return;
+		}
+		const std::string& sel = table_[table_.selection_index()];
+		if (sel == kDefaultEconomyProfile) {
+			delete_.set_tooltip(_("The default profile cannot be deleted"));
+			delete_.set_enabled(false);
+		} else {
+			delete_.set_tooltip(_("Delete the selected profiles"));
+			delete_.set_enabled(true);
+		}
+		profile_name_.set_text(sel);
+		update_save_enabled();
+	}
+
+	void close(bool ok) {
+		end_modal(ok ? UI::Panel::Returncodes::kOk : UI::Panel::Returncodes::kBack);
+		die();
+	}
+
+	void update_table() {
+		table_.clear();
+		for (const auto& pair : economy_options_->get_predefined_targets()) {
+			table_.add(pair.first).set_string(0, _(pair.first));
+		}
+		layout();
+	}
+
+	void save() {
+		const std::string name = profile_name_.text();
+		assert(!name.empty());
+		assert(name != kDefaultEconomyProfile);
+		for (const auto& pair : economy_options_->get_predefined_targets()) {
+			if (pair.first == name) {
+				UI::WLMessageBox m(this, _("Overwrite?"),
+						_("A profile with this name already exists.\nDo you wish to replace it?"),
+						UI::WLMessageBox::MBoxType::kOkCancel);
+				if (m.run<UI::Panel::Returncodes>() != UI::Panel::Returncodes::kOk) {
+					return;
 				}
-			}
-		}
-	}
-}
-
-void EconomyOptionsWindow::EconomyOptionsPanel::reset_target() {
-	Widelands::Game& game = dynamic_cast<Widelands::Game&>(player_->egbase());
-	const bool is_wares = type_ == Widelands::wwWARE;
-	const auto& items = is_wares ? player_->tribe().wares() : player_->tribe().workers();
-	for (const Widelands::DescriptionIndex& index : items) {
-		if (display_.ware_selected(index)) {
-			if (is_wares) {
-				game.send_player_command(*new Widelands::CmdResetWareTargetQuantity(
-				   game.get_gametime(), player_->player_number(), serial_, index));
-			} else {
-				game.send_player_command(*new Widelands::CmdResetWorkerTargetQuantity(
-				   game.get_gametime(), player_->player_number(), serial_, index));
-			}
-		}
-	}
+				break;
+			}
+		}
+		economy_options_->do_create_target(name);
+		close(true);
+	}
+
+	void delete_selected() {
+		assert(table_.has_selection());
+		auto& map = economy_options_->get_predefined_targets();
+		const std::string& name = table_[table_.selection_index()];
+		assert(name != kDefaultEconomyProfile);
+		for (auto it = map.begin(); it != map.end(); ++it) {
+			if (it->first == name) {
+				map.erase(it);
+				break;
+			}
+		}
+		economy_options_->save_targets();
+		update_table();
+	}
+
+	explicit SaveProfileWindow(UI::Panel* parent, EconomyOptionsWindow* eco)
+	   : UI::Window(parent, "save_economy_options_profile", 0, 0, 0, 0, _("Save Profile")),
+	     economy_options_(eco),
+	     main_box_(this, 0, 0, UI::Box::Vertical),
+	     table_box_(&main_box_, 0, 0, UI::Box::Vertical),
+	     table_(&table_box_, 0, 0, 460, 120, UI::PanelStyle::kWui),
+	     buttons_box_(&main_box_, 0, 0, UI::Box::Horizontal),
+	     profile_name_(&buttons_box_, 0, 0, 240, 0, 0, UI::PanelStyle::kWui),
+	     save_(&buttons_box_, "save", 0, 0, 80, 34, UI::ButtonStyle::kWuiPrimary, _("Save")),
+	     cancel_(&buttons_box_, "cancel", 0, 0, 80, 34, UI::ButtonStyle::kWuiSecondary, _("Cancel")),
+	     delete_(&buttons_box_, "delete", 0, 0, 80, 34, UI::ButtonStyle::kWuiSecondary, _("Delete")) {
+		table_.add_column(200, _("Existing Profiles"));
+		update_table();
+
+		table_.selected.connect(boost::bind(&SaveProfileWindow::table_selection_changed, this));
+		profile_name_.changed.connect(boost::bind(&SaveProfileWindow::update_save_enabled, this));
+		profile_name_.ok.connect(boost::bind(&SaveProfileWindow::save, this));
+		profile_name_.cancel.connect(boost::bind(&SaveProfileWindow::close, this, false));
+		save_.sigclicked.connect(boost::bind(&SaveProfileWindow::save, this));
+		cancel_.sigclicked.connect(boost::bind(&SaveProfileWindow::close, this, false));
+		delete_.sigclicked.connect(boost::bind(&SaveProfileWindow::delete_selected, this));
+
+		table_box_.add(&table_, UI::Box::Resizing::kFullSize);
+		buttons_box_.add(&profile_name_, UI::Box::Resizing::kFullSize);
+		buttons_box_.add(&save_);
+		buttons_box_.add(&cancel_);
+		buttons_box_.add(&delete_);
+		main_box_.add(&table_box_, UI::Box::Resizing::kFullSize);
+		main_box_.add(&buttons_box_, UI::Box::Resizing::kFullSize);
+		set_center_panel(&main_box_);
+
+		table_selection_changed();
+		update_save_enabled();
+	}
+	~SaveProfileWindow() {
+	}
+
+private:
+	EconomyOptionsWindow* economy_options_;
+	UI::Box main_box_;
+	UI::Box table_box_;
+	UI::Table<const std::string&> table_;
+	UI::Box buttons_box_;
+	UI::EditBox profile_name_;
+	UI::Button save_;
+	UI::Button cancel_;
+	UI::Button delete_;
+};
+
+void EconomyOptionsWindow::create_target() {
+	std::unique_ptr<SaveProfileWindow> s (new SaveProfileWindow(get_parent(), this));
+	s->run<UI::Panel::Returncodes>();
+}
+
+void EconomyOptionsWindow::do_create_target(const std::string& name) {
+	assert(!name.empty());
+	assert(name != kDefaultEconomyProfile);
+	const Widelands::Tribes& tribes = player_->egbase().tribes();
+	const Widelands::TribeDescr& tribe = player_->tribe();
+	Widelands::Economy* economy = player_->get_economy(serial_);
+	PredefinedTargets t;
+	for (Widelands::DescriptionIndex di : tribe.wares()) {
+		if (tribes.get_ware_descr(di)->has_demand_check(tribe.name())) {
+			t.wares[di] = economy->ware_target_quantity(di).permanent;
+		}
+	}
+	for (Widelands::DescriptionIndex di : tribe.workers()) {
+		if (tribes.get_worker_descr(di)->has_demand_check()) {
+			t.workers[di] = economy->worker_target_quantity(di).permanent;
+		}
+	}
+	predefined_targets_[name] = t;
+
+	save_targets();
+	update_profiles(name);
+}
+
+void EconomyOptionsWindow::save_targets() {
+	const Widelands::Tribes& tribes = player_->egbase().tribes();
+	Profile profile;
+	for (const auto& pair : predefined_targets_) {
+		if (pair.first == kDefaultEconomyProfile) {
+			continue;
+		}
+		Section& section = profile.create_section(pair.first.c_str());
+		for (const auto& setting : pair.second.wares) {
+			section.set_natural(tribes.get_ware_descr(setting.first)->name().c_str(), setting.second);
+		}
+		for (const auto& setting : pair.second.workers) {
+			section.set_natural(tribes.get_worker_descr(setting.first)->name().c_str(), setting.second);
+		}
+	}
+
+	g_fs->ensure_directory_exists(kEconomyProfilesDir);
+	std::string complete_filename = kEconomyProfilesDir + g_fs->file_separator() + player_->tribe().name();
+	profile.write(complete_filename.c_str(), false);
 }

=== modified file 'src/wui/economy_options_window.h'
--- src/wui/economy_options_window.h	2019-02-23 11:00:49 +0000
+++ src/wui/economy_options_window.h	2019-05-04 19:31:09 +0000
@@ -20,20 +20,46 @@
 #ifndef WL_WUI_ECONOMY_OPTIONS_WINDOW_H
 #define WL_WUI_ECONOMY_OPTIONS_WINDOW_H
 
+#include <map>
 #include <memory>
+#include <string>
 
 #include "economy/economy.h"
 #include "logic/map_objects/tribes/tribe_descr.h"
 #include "notifications/notifications.h"
 #include "ui_basic/box.h"
+#include "ui_basic/dropdown.h"
 #include "ui_basic/tabpanel.h"
 #include "ui_basic/window.h"
 #include "wui/waresdisplay.h"
 
+const std::string kDefaultEconomyProfile = "Default";
+
 struct EconomyOptionsWindow : public UI::Window {
 	EconomyOptionsWindow(UI::Panel* parent, Widelands::Economy* economy, bool can_act);
 	~EconomyOptionsWindow();
 
+	struct PredefinedTargets {
+		using Targets = std::map<Widelands::DescriptionIndex, uint32_t>;
+		Targets wares;
+		Targets workers;
+	};
+
+	void create_target();
+	void do_create_target(const std::string&);
+	void save_targets();
+	void read_targets(const std::string& = kDefaultEconomyProfile);
+	void update_profiles(const std::string&);
+	std::map<std::string, PredefinedTargets>& get_predefined_targets() {
+		return predefined_targets_;
+	}
+	const PredefinedTargets& get_selected_target() const {
+		return predefined_targets_.at(dropdown_.get_selected());
+	}
+
+	void change_target(int amount);
+	void reset_target();
+
 private:
 	struct TargetWaresDisplay : public AbstractWaresDisplay {
 		TargetWaresDisplay(UI::Panel* const parent,
@@ -59,6 +85,7 @@
 	 */
 	struct EconomyOptionsPanel : UI::Box {
 		EconomyOptionsPanel(UI::Panel* parent,
+		                    EconomyOptionsWindow* eco_window,
 		                    Widelands::Serial serial,
 		                    Widelands::Player* player,
 		                    bool can_act,
@@ -74,17 +101,23 @@
 		Widelands::WareWorker type_;
 		bool can_act_;
 		TargetWaresDisplay display_;
+		EconomyOptionsWindow* economy_options_window_;
 	};
 
 	/// Actions performed when a NoteEconomyWindow is received.
 	void on_economy_note(const Widelands::NoteEconomy& note);
 
+	UI::Box main_box_;
 	Widelands::Serial serial_;
 	Widelands::Player* player_;
 	UI::TabPanel tabpanel_;
 	EconomyOptionsPanel* ware_panel_;
 	EconomyOptionsPanel* worker_panel_;
 	std::unique_ptr<Notifications::Subscriber<Widelands::NoteEconomy>> economynotes_subscriber_;
+
+	std::map<std::string, PredefinedTargets> predefined_targets_;
+	UI::Box dropdown_box_;
+	UI::Dropdown<std::string> dropdown_;
 };
 
 #endif  // end of include guard: WL_WUI_ECONOMY_OPTIONS_WINDOW_H


Follow ups