示例#1
0
void carryover_info::transfer_to(config& level)
{
	if(!level.has_attribute("next_underlying_unit_id"))
	{
		level["next_underlying_unit_id"] = next_underlying_unit_id_;
	}

	//if the game has been loaded from a snapshot, the existing variables will be the current ones
	if(!level.has_child("variables")) {
		level.add_child("variables", variables_);
	}

	config::attribute_value & seed_value = level["random_seed"];
	if ( seed_value.empty() ) {
		seed_value = rng_.get_random_seed_str();
		level["random_calls"] = rng_.get_random_calls();
	}

	if(!level.has_child("menu_item")){
		for(config& item : wml_menu_items_)
		{
			level.add_child("menu_item").swap(item);
		}
	}

	next_scenario_ = "";
	variables_ = config();
	wml_menu_items_.clear();

}
示例#2
0
void carryover_info::transfer_to(config& level)
{
	if(!level.has_attribute("next_underlying_unit_id"))
	{
		level["next_underlying_unit_id"] = next_underlying_unit_id_;
	}

	//if the game has been loaded from a snapshot, variables_ is empty since we cleared it below.
	level.child_or_add("variables").append(std::move(variables_));

	config::attribute_value & seed_value = level["random_seed"];
	if ( seed_value.empty() ) {
		seed_value = rng_.get_random_seed_str();
		level["random_calls"] = rng_.get_random_calls();
	}

	if(!level.has_child("menu_item")){
		for(config& item : wml_menu_items_)
		{
			level.add_child("menu_item").swap(item);
		}
	}

	next_scenario_ = "";
	variables_.clear();
	wml_menu_items_.clear();

}
示例#3
0
builder_listbox::builder_listbox(const config& cfg)
	: builder_styled_widget(cfg)
	, vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
	, horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
	, header(nullptr)
	, footer(nullptr)
	, list_builder(nullptr)
	, list_data()
	, has_minimum_(cfg["has_minimum"].to_bool(true))
	, has_maximum_(cfg["has_maximum"].to_bool(true))
{
	if(const config& h = cfg.child("header")) {
		header = std::make_shared<builder_grid>(h);
	}

	if(const config& f = cfg.child("footer")) {
		footer = std::make_shared<builder_grid>(f);
	}

	const config& l = cfg.child("list_definition");

	VALIDATE(l, _("No list defined."));

	list_builder = std::make_shared<builder_grid>(l);
	assert(list_builder);

	VALIDATE(list_builder->rows == 1, _("A 'list_definition' should contain one row."));

	if(cfg.has_child("list_data")) {
		list_data = parse_list_data(cfg.child("list_data"), list_builder->cols);
	}
}
示例#4
0
//changes done during 1.11.0-dev
static void convert_old_saves_1_11_0(config& cfg)
{
	if(!cfg.has_child("snapshot")){
		return;
	}

	const config& snapshot = cfg.child("snapshot");
	const config& replay_start = cfg.child("replay_start");
	const config& replay = cfg.child("replay");

	if(!cfg.has_child("carryover_sides") && !cfg.has_child("carryover_sides_start")){
		config carryover;
		//copy rng and menu items from toplevel to new carryover_sides
		carryover["random_seed"] = cfg["random_seed"];
		carryover["random_calls"] = cfg["random_calls"];
		BOOST_FOREACH(const config& menu_item, cfg.child_range("menu_item")){
			carryover.add_child("menu_item", menu_item);
		}
		carryover["difficulty"] = cfg["difficulty"];
		carryover["random_mode"] = cfg["random_mode"];
		//the scenario to be played is always stored as next_scenario in carryover_sides_start
		carryover["next_scenario"] = cfg["scenario"];

		config carryover_start = carryover;

		//copy sides from either snapshot or replay_start to new carryover_sides
		if(!snapshot.empty()){
			BOOST_FOREACH(const config& side, snapshot.child_range("side")){
				carryover.add_child("side", side);
			}
			//for compatibility with old savegames that use player instead of side
			BOOST_FOREACH(const config& side, snapshot.child_range("player")){
				carryover.add_child("side", side);
			}
			//save the sides from replay_start in carryover_sides_start
			BOOST_FOREACH(const config& side, replay_start.child_range("side")){
				carryover_start.add_child("side", side);
			}
			//for compatibility with old savegames that use player instead of side
			BOOST_FOREACH(const config& side, replay_start.child_range("player")){
				carryover_start.add_child("side", side);
			}
		} else if (!replay_start.empty()){
示例#5
0
void editor_controller::init_music(const config& game_config)
{
	const std::string tag_name = "editor_music";
	if (!game_config.has_child(tag_name))
		ERR_ED << "No editor music defined\n";
	else {
		BOOST_FOREACH(const config& editor_music, game_config.child_range(tag_name)) {
			BOOST_FOREACH(const config& music, editor_music.child_range("music")) {
				music_tracks_.push_back(sound::music_track(music));
			}
		}
	}
}
builder_stacked_widget::builder_stacked_widget(const config& real_cfg)
	: builder_styled_widget(real_cfg), stack()
{
	const config& cfg = real_cfg.has_child("stack") ? real_cfg.child("stack") : real_cfg;
	if(&cfg != &real_cfg) {
		lg::wml_error() << "Stacked widgets no longer require a [stack] tag. Instead, place [layer] tags directly in the widget definition.\n";
	}
	VALIDATE(cfg.has_child("layer"), _("No stack layers defined."));
	for(const auto & layer : cfg.child_range("layer"))
	{
		stack.emplace_back(std::make_shared<builder_grid>(layer));
	}
}
示例#7
0
void editor_controller::init_music(const config& game_config)
{
	const std::string tag_name = "editor_music";
	if (!game_config.has_child(tag_name))
		ERR_ED << "No editor music defined\n";
	else {
		BOOST_FOREACH(const config& editor_music, game_config.child_range(tag_name)) {
			BOOST_FOREACH(const config& music, editor_music.child_range("music")) {
				sound::music_track track(music);
				if (track.file_path().empty())
					WRN_ED << "Music track " << track.id() << " not found.\n";
				else
					music_tracks_.push_back(sound::music_track(music));
			}
		}
	}
}
示例#8
0
random_map::random_map(const config& data) :
	scenario(data),
	generator_data_(),
	generate_whole_scenario_(data_.has_attribute("scenario_generation")),
	generator_name_(generate_whole_scenario_ ? data_["scenario_generation"] : data_["map_generation"])
{
	if (!data.has_child("generator")) {
		data_ = config();
		generator_data_= config();
		data_["description"] = "Error: Random map found with missing generator information. Scenario should have a [generator] child.";
		data_["error_message"] = "missing [generator] tag";
	} else {
		generator_data_ = data.child("generator");
	}

	if (!data.has_attribute("scenario_generation") && !data.has_attribute("map_generation")) {
		data_ = config();
		generator_data_= config();
		data_["description"] = "Error: Random map found with missing generator information. Scenario should have a [generator] child.";
		data_["error_message"] = "couldn't find 'scenario_generation' or 'map_generation' attribute";
	}
}
示例#9
0
tbuilder_horizontal_listbox::tbuilder_horizontal_listbox(const config& cfg)
	: tbuilder_control(cfg)
	, vertical_scrollbar_mode(
			  get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
	, horizontal_scrollbar_mode(
			  get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
	, list_builder(nullptr)
	, list_data()
	, has_minimum_(cfg["has_minimum"].to_bool(true))
	, has_maximum_(cfg["has_maximum"].to_bool(true))
{
	const config& l = cfg.child("list_definition");

	VALIDATE(l, _("No list defined."));
	list_builder = std::make_shared<tbuilder_grid>(l);
	assert(list_builder);
	VALIDATE(list_builder->rows == 1,
			 _("A 'list_definition' should contain one row."));

	if(cfg.has_child("list_data")) {
		list_data = parse_list_data(cfg.child("list_data"), list_builder->cols);
	}
}
示例#10
0
turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg)
{
	// the simple wesnothserver implementation in wesnoth was removed years ago.
	assert(cfg.all_children_count() == 1);
	assert(cfg.attribute_range().first == cfg.attribute_range().second);
	if(!resources::recorder->at_end())
	{
		ERR_NW << "processing network data while still having data on the replay." << std::endl;
	}

	if (const config &msg = cfg.child("message"))
	{
		resources::screen->get_chat_manager().add_chat_message(time(nullptr), msg["sender"], msg["side"],
				msg["message"], events::chat_handler::MESSAGE_PUBLIC,
				preferences::message_bell());
	}
	else if (const config &msg = cfg.child("whisper") /*&& is_observer()*/)
	{
		resources::screen->get_chat_manager().add_chat_message(time(nullptr), "whisper: " + msg["sender"].str(), 0,
				msg["message"], events::chat_handler::MESSAGE_PRIVATE,
				preferences::message_bell());
	}
	else if (const config &ob = cfg.child("observer") )
	{
		resources::screen->get_chat_manager().add_observer(ob["name"]);
	}
	else if (const config &ob = cfg.child("observer_quit"))
	{
		resources::screen->get_chat_manager().remove_observer(ob["name"]);
	}
	else if (cfg.child("leave_game")) {
		throw ingame_wesnothd_error("");
	}
	else if (const config &turn = cfg.child("turn"))
	{
		return handle_turn(turn);
	}
	else if (cfg.has_child("whiteboard"))
	{
		resources::whiteboard->process_network_data(cfg);
	}
	else if (const config &change = cfg.child("change_controller"))
	{
		if(change.empty()) {
			ERR_NW << "Bad [change_controller] signal from server, [change_controller] tag was empty." << std::endl;
			return PROCESS_CONTINUE;
		}

		const int side = change["side"].to_int();
		const bool is_local = change["is_local"].to_bool();
		const std::string player = change["player"];
		const size_t index = side - 1;
		if(index >= resources::gameboard->teams().size()) {
			ERR_NW << "Bad [change_controller] signal from server, side out of bounds: " << change.debug() << std::endl;
			return PROCESS_CONTINUE;
		}

		const team & tm = resources::gameboard->teams().at(index);
		const bool was_local = tm.is_local();

		resources::gameboard->side_change_controller(side, is_local, player);

		if (!was_local && tm.is_local()) {
			resources::controller->on_not_observer();
		}

		if (resources::gameboard->is_observer() || (resources::gameboard->teams())[resources::screen->playing_team()].is_local_human()) {
			resources::screen->set_team(resources::screen->playing_team());
			resources::screen->redraw_everything();
			resources::screen->recalculate_minimap();
		} else if (tm.is_local_human()) {
			resources::screen->set_team(side - 1);
			resources::screen->redraw_everything();
			resources::screen->recalculate_minimap();
		}

		resources::whiteboard->on_change_controller(side,tm);

		resources::screen->labels().recalculate_labels();

		const bool restart = resources::screen->playing_side() == side && (was_local || tm.is_local());
		return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
	}

	else if (const config &side_drop_c = cfg.child("side_drop"))
	{
		const int  side_drop = side_drop_c["side_num"].to_int(0);
		size_t index = side_drop -1;

		bool restart = side_drop == resources::screen->playing_side();

		if (index >= resources::teams->size()) {
			ERR_NW << "unknown side " << side_drop << " is dropping game" << std::endl;
			throw ingame_wesnothd_error("");
		}

		team::CONTROLLER ctrl;
		if(!ctrl.parse(side_drop_c["controller"])) {
			ERR_NW << "unknown controller type issued from server on side drop: " << side_drop_c["controller"] << std::endl;
			throw ingame_wesnothd_error("");
		}
		
		if (ctrl == team::CONTROLLER::AI) {
			resources::gameboard->side_drop_to(side_drop, ctrl);
			return restart ? PROCESS_RESTART_TURN:PROCESS_CONTINUE;
		}
		//null controlled side cannot be dropped becasue they aren't controlled by anyone.
		else if (ctrl != team::CONTROLLER::HUMAN) {
			ERR_NW << "unknown controller type issued from server on side drop: " << ctrl.to_cstring() << std::endl;
			throw ingame_wesnothd_error("");
		}

		int action = 0;
		int first_observer_option_idx = 0;
		int control_change_options = 0;
		bool has_next_scenario = !resources::gamedata->next_scenario().empty() && resources::gamedata->next_scenario() != "null";

		std::vector<std::string> observers;
		std::vector<const team *> allies;
		std::vector<std::string> options;

		const team &tm = resources::gameboard->teams()[index];

		for (const team &t : resources::gameboard->teams()) {
			if (!t.is_enemy(side_drop) && !t.is_local_human() && !t.is_local_ai() && !t.is_network_ai() && !t.is_empty()
				&& t.current_player() != tm.current_player()) {
				allies.push_back(&t);
			}
		}

		// We want to give host chance to decide what to do for side
		if (!resources::controller->is_linger_mode() || has_next_scenario) {
			utils::string_map t_vars;

			//get all allies in as options to transfer control
			for (const team *t : allies) {
				//if this is an ally of the dropping side and it is not us (choose local player
				//if you want that) and not ai or empty and if it is not the dropping side itself,
				//get this team in as well
				t_vars["player"] = t->current_player();
				options.push_back(vgettext("Give control to their ally $player", t_vars));
				control_change_options++;
			}

			first_observer_option_idx = options.size();

			//get all observers in as options to transfer control
			for (const std::string &ob : resources::screen->observers()) {
				t_vars["player"] = ob;
				options.push_back(vgettext("Give control to observer $player", t_vars));
				observers.push_back(ob);
				control_change_options++;
			}

			options.push_back(_("Replace with AI"));
			options.push_back(_("Replace with local player"));
			options.push_back(_("Set side to idle"));
			options.push_back(_("Save and abort game"));

			t_vars["player"] = tm.current_player();
			const std::string msg =  vgettext("$player has left the game. What do you want to do?", t_vars);
			gui2::tsimple_item_selector dlg("", msg, options);
			dlg.set_single_button(true);
			dlg.show(resources::screen->video());
			action = dlg.selected_index();

			// If esc was pressed, default to setting side to idle
			if (action == -1) {
				action = control_change_options + 2;
			}
		} else {
			// Always set leaving side to idle if in linger mode and there is no next scenario
			action = 2;
		}

		if (action < control_change_options) {
			// Grant control to selected ally
			
			{
				// Server thinks this side is ours now so in case of error transferring side we have to make local state to same as what server thinks it is.
				resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_IDLE);
			}

			if (action < first_observer_option_idx) {
				change_side_controller(side_drop, allies[action]->current_player());
			} else {
				change_side_controller(side_drop, observers[action - first_observer_option_idx]);
			}

			return restart ? PROCESS_RESTART_TURN : PROCESS_CONTINUE;
		} else {
			action -= control_change_options;

			//make the player an AI, and redo this turn, in case
			//it was the current player's team who has just changed into
			//an AI.
			switch(action) {
				case 0:
					resources::controller->on_not_observer();
					resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_AI);

					return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;

				case 1:
					resources::controller->on_not_observer();
					resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_HUMAN);

					return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;
				case 2:
					resources::gameboard->side_drop_to(side_drop, team::CONTROLLER::HUMAN, team::PROXY_CONTROLLER::PROXY_IDLE);

					return restart?PROCESS_RESTART_TURN:PROCESS_CONTINUE;

				case 3:
					//The user pressed "end game". Don't throw a network error here or he will get
					//thrown back to the title screen.
					do_save();
					throw_quit_game_exception();
				default:
					break;
			}
		}
	}

	// The host has ended linger mode in a campaign -> enable the "End scenario" button
	// and tell we did get the notification.
	else if (cfg.child("notify_next_scenario")) {
		gui::button* btn_end = resources::screen->find_action_button("button-endturn");
		if(btn_end) {
			btn_end->enable(true);
		}
		return PROCESS_END_LINGER;
	}

	//If this client becomes the new host, notify the play_controller object about it
	else if (cfg.child("host_transfer")){
		host_transfer_.notify_observers();
	}
	else
	{
		ERR_NW << "found unknown command:\n" << cfg.debug() << std::endl;
	}

	return PROCESS_CONTINUE;
}
void mp_options_helper::display_custom_options(const std::string& type, int node_position, const config& cfg)
{
	// Needed since some compilers don't like passing just {}
	static const std::map<std::string, string_map> empty_map;

	// This ensures that any game, era, or mod with no options doesn't get an entry in the visible_options_
	// vector and prevents invalid options from different games, era, or mods being created when the options
	// config is created.
	if(!cfg.has_child("options")) {
		return;
	}

	visible_options_.push_back({type, cfg["id"]});

	// Get the node vector for this specific source type
	node_vector& type_node_vector = node_data_map_[type].nodes;

	for(const auto& options : cfg.child_range("options")) {
		std::map<std::string, string_map> data;
		string_map item;

		item["label"] = cfg["name"];
		data.emplace("tree_view_node_label", item);

		tree_view_node& option_node = options_tree_.add_node("option_node", data, node_position);
		type_node_vector.push_back(&option_node);

		for(const config::any_child& opt : options.all_children_range()) {
			data.clear();
			item.clear();

			const config& option_cfg = opt.cfg;

			const auto add_name = [&](const std::string& id) {
				item["label"] = option_cfg["name"];
				data.emplace(id, item);
			};

			config::attribute_value val;

			if(opt.key == "checkbox") {
				add_name("option_checkbox");

				toggle_button* checkbox;
				std::tie(checkbox, val) = add_node_and_get_widget<toggle_button>(option_node, "option_checkbox", data, option_cfg);

				checkbox->set_value(val.to_bool());

				connect_signal_notify_modified(*checkbox,
					std::bind(&mp_options_helper::update_options_data_map<toggle_button>, this, checkbox, visible_options_.back()));

			} else if(opt.key == "spacer") {
				option_node.add_child("options_spacer_node", empty_map);

			} else if(opt.key == "choice" || opt.key == "combo") {
				if(opt.key == "combo") {
					deprecated_message("combo", DEP_LEVEL::FOR_REMOVAL, {1, 15, 0}, "Use [choice] instead.");
				}

				if(!option_cfg.has_child("item")) {
					continue;
				}

				add_name("menu_button_label");

				std::vector<config> combo_items;
				std::vector<std::string> combo_values;

				for(auto i : option_cfg.child_range("item")) {
					// Comboboxes expect this key to be 'label' not 'name'
					i["label"] = i["name"];

					combo_items.push_back(i);
					combo_values.push_back(i["value"]);
				}

				menu_button* menu;
				std::tie(menu, val) = add_node_and_get_widget<menu_button>(option_node, "option_menu_button", data, option_cfg);

				// Needs to be called before set_selected
				menu->set_values(combo_items);

				auto iter = std::find(combo_values.begin(), combo_values.end(), val.str());

				if(iter != combo_values.end()) {
					menu->set_selected(std::distance(combo_values.begin(), iter));
				}

				connect_signal_notify_modified(*menu,
					std::bind(&mp_options_helper::update_options_data_map_menu_button, this, menu, visible_options_.back(), option_cfg));

			} else if(opt.key == "slider") {
				add_name("slider_label");

				slider* slide;
				std::tie(slide, val) = add_node_and_get_widget<slider>(option_node, "option_slider", data, option_cfg);

				slide->set_value_range(option_cfg["min"].to_int(), option_cfg["max"].to_int());
				slide->set_step_size(option_cfg["step"].to_int(1));
				slide->set_value(val.to_int());

				connect_signal_notify_modified(*slide,
					std::bind(&mp_options_helper::update_options_data_map<slider>, this, slide, visible_options_.back()));

			} else if(opt.key == "entry") {
				add_name("text_entry_label");

				text_box* textbox;
				std::tie(textbox, val) = add_node_and_get_widget<text_box>(option_node, "option_text_entry", data, option_cfg);

				textbox->set_value(val.str());
				textbox->set_text_changed_callback(
					std::bind(&mp_options_helper::update_options_data_map<text_box>, this, textbox, visible_options_.back()));
			}
		}

		// Add the Defaults button at the end
		tree_view_node& node = option_node.add_child("options_default_button", empty_map);

		connect_signal_mouse_left_click(find_widget<button>(&node, "reset_option_values", false),
			std::bind(&mp_options_helper::reset_options_data, this, visible_options_.back(),
				std::placeholders::_3, std::placeholders::_4));
	}
}
std::string default_map_generator_job::default_generate_map(generator_data data, std::map<map_location,std::string>* labels, const config& cfg)
{
	log_scope("map generation");

	// Odd widths are nasty
	VALIDATE(is_even(data.width), _("Random maps with an odd width aren't supported."));

	// Try to find configuration for castles
	const config& castle_config = cfg.child("castle");

	int ticks = SDL_GetTicks();

	// We want to generate a map that is 9 times bigger than the actual size desired.
	// Only the middle part of the map will be used, but the rest is so that the map we
	// end up using can have a context (e.g. rivers flowing from out of the map into the map,
	// same for roads, etc.)
	data.width  *= 3;
	data.height *= 3;

	config naming;

	if(cfg.has_child("naming")) {
		naming = game_config_.child("naming");
		naming.append_attributes(cfg.child("naming"));
	}

	// If the [naming] child is empty, we cannot provide good names.
	std::map<map_location,std::string>* misc_labels = naming.empty() ? nullptr : labels;

	std::shared_ptr<name_generator>
		base_name_generator, river_name_generator, lake_name_generator,
		road_name_generator, bridge_name_generator, mountain_name_generator,
		forest_name_generator, swamp_name_generator;

	if(misc_labels != nullptr) {
		name_generator_factory base_generator_factory{ naming, {"male", "base", "bridge", "road", "river", "forest", "lake", "mountain", "swamp"} };

		naming.get_old_attribute("base_names", "male_names", "[naming]male_names= is deprecated, use base_names= instead");
		//Due to the attribute detection feature of the factory we also support male_name_generator= but keep it undocumented.

		base_name_generator = base_generator_factory.get_name_generator( (naming.has_attribute("base_names") || naming.has_attribute("base_name_generator")) ? "base" : "male" );
		river_name_generator    = base_generator_factory.get_name_generator("river");
		lake_name_generator     = base_generator_factory.get_name_generator("lake");
		road_name_generator     = base_generator_factory.get_name_generator("road");
		bridge_name_generator   = base_generator_factory.get_name_generator("bridge");
		mountain_name_generator = base_generator_factory.get_name_generator("mountain");
		forest_name_generator   = base_generator_factory.get_name_generator("forest");
		swamp_name_generator    = base_generator_factory.get_name_generator("swamp");
	}

	// Generate the height of everything.
	const height_map heights = generate_height_map(data.width, data.height, data.iterations, data.hill_size, data.island_size, data.island_off_center);

	LOG_NG << "Done generating height map. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	ticks = SDL_GetTicks();

	// Find out what the 'flatland' on this map is, i.e. grassland.
	std::string flatland = cfg["default_flatland"];
	if(flatland.empty()) {
		flatland = t_translation::write_terrain_code(t_translation::GRASS_LAND);
	}

	const t_translation::terrain_code grassland = t_translation::read_terrain_code(flatland);

	std::vector<terrain_height_mapper> height_conversion;
	for(const config& h : cfg.child_range("height")) {
		height_conversion.emplace_back(h);
	}

	terrain_map terrain(data.width, data.height, grassland);
	for(size_t x = 0; x != heights.size(); ++x) {
		for(size_t y = 0; y != heights[x].size(); ++y) {
			for(auto i : height_conversion) {
				if(i.convert_terrain(heights[x][y])) {
					terrain[x][y] = i.convert_to();
					break;
				}
			}
		}
	}

	t_translation::starting_positions starting_positions;
	LOG_NG << output_map(terrain, starting_positions);
	LOG_NG << "Placed landforms. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	ticks = SDL_GetTicks();

	/* Now that we have our basic set of flatland/hills/mountains/water,
	 * we can place lakes and rivers on the map.
	 * All rivers are sourced at a lake.
	 * Lakes must be in high land - at least 'min_lake_height'.
	 * (Note that terrain below a certain altitude may be made into bodies of water
	 *  in the code above - i.e. 'sea', but these are not considered 'lakes',
	 *  because they are not sources of rivers).
	 *
	 * We attempt to place 'max_lakes' lakes.
	 * Each lake will be placed at a random location, if that random location meets theminimum
	 * terrain requirements for a lake. We will also attempt to source a river from each lake.
	 */
	std::set<map_location> lake_locs;

	std::map<map_location, std::string> river_names, lake_names, road_names, bridge_names, mountain_names, forest_names, swamp_names;

	const size_t nlakes = data.max_lakes > 0 ? (rng_()%data.max_lakes) : 0;
	for(size_t lake = 0; lake != nlakes; ++lake) {
		for(int tries = 0; tries != 100; ++tries) {
			const int x = rng_()%data.width;
			const int y = rng_()%data.height;

			if(heights[x][y] <= cfg["min_lake_height"].to_int()) {
				continue;
			}

			std::vector<map_location> river = generate_river(heights, terrain, x, y, cfg["river_frequency"]);

			if(!river.empty() && misc_labels != nullptr) {
				const std::string base_name = base_name_generator->generate();
				const std::string& name = river_name_generator->generate({{"base",  base_name}});
				LOG_NG << "Named river '" << name << "'\n";

				size_t name_frequency = 20;
				for(std::vector<map_location>::const_iterator r = river.begin(); r != river.end(); ++r) {
					const map_location loc(r->x-data.width/3,r->y-data.height/3);

					if(((r - river.begin())%name_frequency) == name_frequency/2) {
						misc_labels->emplace(loc, name);
					}

					river_names.emplace(loc, base_name);
				}
			}

			LOG_NG << "Generating lake...\n";

			std::set<map_location> locs;
			if(generate_lake(terrain, x, y, cfg["lake_size"], locs) && misc_labels != nullptr) {
				bool touches_other_lake = false;

				std::string base_name = base_name_generator->generate();
				const std::string& name = lake_name_generator->generate({{"base",  base_name}});

				// Only generate a name if the lake hasn't touched any other lakes,
				// so that we don't end up with one big lake with multiple names.
				for(auto i : locs) {
					if(lake_locs.count(i) != 0) {
						touches_other_lake = true;

						// Reassign the name of this lake to be the same as the other lake
						const map_location loc(i.x-data.width/3,i.y-data.height/3);
						const std::map<map_location,std::string>::const_iterator other_name = lake_names.find(loc);
						if(other_name != lake_names.end()) {
							base_name = other_name->second;
						}
					}

					lake_locs.insert(i);
				}

				if(!touches_other_lake) {
					const map_location loc(x-data.width/3,y-data.height/3);
					misc_labels->erase(loc);
					misc_labels->emplace(loc, name);
				}

				for(auto i : locs) {
					const map_location loc(i.x-data.width/3,i.y-data.height/3);
					lake_names.emplace(loc, base_name);
				}
			}

			break;
		}
	}

	LOG_NG << "Generated rivers. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	ticks = SDL_GetTicks();

	const size_t default_dimensions = 40*40*9;

	/*
	 * Convert grassland terrain to other types of flat terrain.
	 *
	 * We generate a 'temperature map' which uses the height generation
	 * algorithm to generate the temperature levels all over the map.  Then we
	 * can use a combination of height and terrain to divide terrain up into
	 * more interesting types than the default.
	 */
	const height_map temperature_map = generate_height_map(data.width,data.height,
		cfg["temperature_iterations"].to_int() * data.width * data.height / default_dimensions,
		cfg["temperature_size"], 0, 0);

	LOG_NG << "Generated temperature map. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	ticks = SDL_GetTicks();

	std::vector<terrain_converter> converters;
	for(const config& cv : cfg.child_range("convert")) {
		converters.emplace_back(cv);
	}

	LOG_NG << "Created terrain converters. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	ticks = SDL_GetTicks();

	// Iterate over every flatland tile, and determine what type of flatland it is, based on our [convert] tags.
	for(int x = 0; x != data.width; ++x) {
		for(int y = 0; y != data.height; ++y) {
			for(auto i : converters) {
				if(i.convert_terrain(terrain[x][y],heights[x][y],temperature_map[x][y])) {
					terrain[x][y] = i.convert_to();
					break;
				}
			}
		}
	}

	LOG_NG << "Placing castles...\n";

	/*
	 * Attempt to place castles at random.
	 *
	 * After they are placed, we run a sanity check to make sure no two castles
	 * are closer than 'min_distance' hexes apart, and that they appear on a
	 * terrain listed in 'valid_terrain'.
	 *
	 * If not, we attempt to place them again.
	 */
	std::vector<map_location> castles;
	std::set<map_location> failed_locs;

	if(castle_config) {
		/*
		 * Castle configuration tag contains a 'valid_terrain' attribute which is a
		 * list of terrains that the castle may appear on.
		 */
		const t_translation::ter_list list = t_translation::read_list(castle_config["valid_terrain"]);

		const is_valid_terrain terrain_tester(terrain, list);

		for(int player = 0; player != data.nplayers; ++player) {
			LOG_NG << "placing castle for " << player << "\n";
			lg::scope_logger inner_scope_logging_object__(lg::general(), "placing castle");
			const int min_x = data.width/3 + 3;
			const int min_y = data.height/3 + 3;
			const int max_x = (data.width/3)*2 - 4;
			const int max_y = (data.height/3)*2 - 4;
			int min_distance = castle_config["min_distance"];

			map_location best_loc;
			int best_ranking = 0;
			for(int x = min_x; x != max_x; ++x) {
				for(int y = min_y; y != max_y; ++y) {
					const map_location loc(x,y);
					if(failed_locs.count(loc)) {
						continue;
					}

					const int ranking = rank_castle_location(x, y, terrain_tester, min_x, max_x, min_y, max_y, min_distance, castles, best_ranking);
					if(ranking <= 0) {
						failed_locs.insert(loc);
					}

					if(ranking > best_ranking) {
						best_ranking = ranking;
						best_loc = loc;
					}
				}
			}

			if(best_ranking == 0) {
				ERR_NG << "No castle location found, aborting." << std::endl;
				const std::string error = _("No valid castle location found. Too many or too few mountain hexes? (please check the 'max hill size' parameter)");
				throw mapgen_exception(error);
			}

			assert(std::find(castles.begin(), castles.end(), best_loc) == castles.end());
			castles.push_back(best_loc);

			// Make sure the location can't get a second castle.
			failed_locs.insert(best_loc);
		}

		LOG_NG << "Placed castles. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	}
	LOG_NG << "Placing roads...\n";
	ticks = SDL_GetTicks();

	// Place roads.
	// We select two tiles at random locations on the borders of the map
	// and try to build roads between them.
	int nroads = cfg["roads"];
	if(data.link_castles) {
		nroads += castles.size()*castles.size();
	}

	std::set<map_location> bridges;

	road_path_calculator calc(terrain, cfg, rng_());
	for(int road = 0; road != nroads; ++road) {
		lg::scope_logger another_inner_scope_logging_object__(lg::general(), "creating road");

		/*
		 * We want the locations to be on the portion of the map we're actually
		 * going to use, since roads on other parts of the map won't have any
		 * influence, and doing it like this will be quicker.
		 */
		map_location src = random_point_at_side(data.width/3 + 2,data.height/3 + 2);
		map_location dst = random_point_at_side(data.width/3 + 2,data.height/3 + 2);

		src.x += data.width/3 - 1;
		src.y += data.height/3 - 1;
		dst.x += data.width/3 - 1;
		dst.y += data.height/3 - 1;

		if(data.link_castles && road < int(castles.size() * castles.size())) {
			const size_t src_castle = road/castles.size();
			const size_t dst_castle = road%castles.size();
			if(src_castle >= dst_castle) {
				continue;
			}

			src = castles[src_castle];
			dst = castles[dst_castle];
		} else if(src.x == dst.x || src.y == dst.y) {
			// If the road isn't very interesting (on the same border), don't draw it.
			continue;
		}

		if(calc.cost(src, 0.0) >= 1000.0 || calc.cost(dst, 0.0) >= 1000.0) {
			continue;
		}

		// Search a path out for the road
		pathfind::plain_route rt = pathfind::a_star_search(src, dst, 10000.0, calc, data.width, data.height);

		const std::string& road_base_name = misc_labels != nullptr
			? base_name_generator->generate()
			: "";
		const std::string& road_name = misc_labels != nullptr
			? road_name_generator->generate({{"base", road_base_name}})
			: "";
		const int name_frequency = 20;
		int name_count = 0;

		bool on_bridge = false;

		// Draw the road.
		// If the search failed, rt.steps will simply be empty.
		for(std::vector<map_location>::const_iterator step = rt.steps.begin();
				step != rt.steps.end(); ++step) {

			const int x = step->x;
			const int y = step->y;

			if(x < 0 || y < 0 || x >= static_cast<long>(data.width) || y >= static_cast<long>(data.height)) {
				continue;
			}

			// Find the configuration which tells us what to convert this tile to, to make it into a road.
			const config& child = cfg.find_child("road_cost", "terrain", t_translation::write_terrain_code(terrain[x][y]));
			if(child.empty()){
				continue;
			}

			/* Convert to bridge means that we want to convert depending on the direction of the road.
			 * Typically it will be in a format like convert_to_bridge = \,|,/
			 * '|' will be used if the road is going north-south
			 * '/' will be used if the road is going south west-north east
			 * '\' will be used if the road is going south east-north west
			 * The terrain will be left unchanged otherwise (if there is no clear direction).
			 */
			const std::string& convert_to_bridge = child["convert_to_bridge"];
			if(!convert_to_bridge.empty()) {
				if(step == rt.steps.begin() || step+1 == rt.steps.end()) {
					continue;
				}

				const map_location& last = *(step-1);
				const map_location& next = *(step+1);

				map_location adj[6];
				get_adjacent_tiles(*step,adj);

				int direction = -1;

				// If we are going north-south
				if((last == adj[0] && next == adj[3]) || (last == adj[3] && next == adj[0])) {
					direction = 0;
				}

				// If we are going south west-north east
				else if((last == adj[1] && next == adj[4]) || (last == adj[4] && next == adj[1])) {
					direction = 1;
				}

				// If we are going south east-north west
				else if((last == adj[2] && next == adj[5]) || (last == adj[5] && next == adj[2])) {
					direction = 2;
				}

				if(misc_labels != nullptr && !on_bridge) {
					on_bridge = true;
					std::string bridge_base_name = base_name_generator->generate();
					const std::string& name = bridge_name_generator->generate({{"base",  bridge_base_name}});
					const map_location loc(x - data.width / 3, y-data.height/3);
					misc_labels->emplace(loc, name);
					bridge_names.emplace(loc, bridge_base_name); //add to use for village naming
					bridges.insert(loc);
				}

				if(direction != -1) {
					const std::vector<std::string> items = utils::split(convert_to_bridge);
					if(size_t(direction) < items.size() && !items[direction].empty()) {
						terrain[x][y] = t_translation::read_terrain_code(items[direction]);
					}

					continue;
				}
			} else {
				on_bridge = false;
			}

			// Just a plain terrain substitution for a road
			const std::string& convert_to = child["convert_to"];
			if(!convert_to.empty()) {
				const t_translation::terrain_code letter = t_translation::read_terrain_code(convert_to);
				if(misc_labels != nullptr && terrain[x][y] != letter && name_count++ == name_frequency && !on_bridge) {
					misc_labels->emplace(map_location(x - data.width / 3, y - data.height / 3), road_name);
					name_count = 0;
				}

				terrain[x][y] = letter;
				if(misc_labels != nullptr) {
					const map_location loc(x - data.width / 3, y - data.height / 3); //add to use for village naming
					if(!road_base_name.empty())
						road_names.emplace(loc, road_base_name);
				}
			}
		}
	}

	// Now that road drawing is done, we can plonk down the castles.
	for(std::vector<map_location>::const_iterator c = castles.begin(); c != castles.end(); ++c) {
		if(!c->valid()) {
			continue;
		}

		const int x = c->x;
		const int y = c->y;
		const int player = c - castles.begin() + 1;
		const t_translation::coordinate coord(x, y);
		starting_positions.insert(t_translation::starting_positions::value_type(std::to_string(player), coord));
		terrain[x][y] = t_translation::HUMAN_KEEP;

		const int castle_array[13][2] {
			{-1, 0}, {-1, -1}, {0, -1}, {1, -1}, {1, 0}, {0, 1}, {-1, 1},
			{-2, 1}, {-2, 0}, {-2, -1}, {-1, -2}, {0, -2}, {1, -2}
		};

		for(int i = 0; i < data.castle_size - 1; i++) {
			terrain[x+ castle_array[i][0]][y+ castle_array[i][1]] = t_translation::HUMAN_CASTLE;
		}

		// Remove all labels under the castle tiles
		if(labels != nullptr) {
			labels->erase(map_location(x-data.width/3,y-data.height/3));
			for(int i = 0; i < data.castle_size - 1; i++) {
				labels->erase(map_location(x+ castle_array[i][0]-data.width/3, y+ castle_array[i][1]-data.height/3));
			}
		}
	}

	LOG_NG << "Placed roads. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	ticks = SDL_GetTicks();

	/* Random naming for landforms: mountains, forests, swamps, hills
	 * we name these now that everything else is placed (as e.g., placing
	 * roads could split a forest)
	 */
	if(misc_labels != nullptr) {
		for(int x = data.width / 3; x < (data.width / 3)*2; x++) {
			for(int y = data.height / 3; y < (data.height / 3) * 2;y++) {
				//check the terrain of the tile
				const map_location loc(x - data.width / 3, y - data.height / 3);
				const t_translation::terrain_code terr = terrain[x][y];
				std::string name, base_name;
				std::set<std::string> used_names;

				if(t_translation::terrain_matches(terr, t_translation::ALL_MOUNTAINS)) {
					//name every 15th mountain
					if((rng_() % 15) == 0) {
						for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) {
							base_name = base_name_generator->generate();
							name = mountain_name_generator->generate({{"base",  base_name}});
						}
						misc_labels->emplace(loc, name);
						mountain_names.emplace(loc, base_name);
					}
				} else if(t_translation::terrain_matches(terr, t_translation::ALL_FORESTS)) {
					// If the forest tile is not named yet, name it
					const std::map<map_location, std::string>::const_iterator forest_name = forest_names.find(loc);
					if(forest_name == forest_names.end()) {
						for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) {
							base_name = base_name_generator->generate();
							name = forest_name_generator->generate({{"base",  base_name}});
						}
						forest_names.emplace(loc, base_name);
						// name all connected forest tiles accordingly
						flood_name(loc, base_name, forest_names, t_translation::ALL_FORESTS, terrain, data.width, data.height, 0, misc_labels, name);
					}
				} else if(t_translation::terrain_matches(terr, t_translation::ALL_SWAMPS)) {
					// If the swamp tile is not named yet, name it
					const std::map<map_location, std::string>::const_iterator swamp_name = swamp_names.find(loc);
					if(swamp_name == swamp_names.end()) {
						for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) {
							base_name = base_name_generator->generate();
							name = swamp_name_generator->generate({{"base",  base_name}});
						}
						swamp_names.emplace(loc, base_name);
						// name all connected swamp tiles accordingly
						flood_name(loc, base_name, swamp_names, t_translation::ALL_SWAMPS, terrain, data.width, data.height, 0, misc_labels, name);
					}
				}
			}
		}
	}

	LOG_NG << "Named landforms. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";
	LOG_NG << "Placing villages...\n";
	ticks = SDL_GetTicks();

	/*
	 * Place villages in a 'grid', to make placing fair, but with villages
	 * displaced from their position according to terrain and randomness, to
	 * add some variety.
	 */
	std::set<map_location> villages;

	if(data.nvillages > 0) {

		// First we work out the size of the x and y distance between villages
		const size_t tiles_per_village = ((data.width*data.height)/9)/data.nvillages;
		size_t village_x = 1, village_y = 1;

		// Alternate between incrementing the x and y value.
		// When they are high enough to equal or exceed the tiles_per_village,
		// then we have them to the value we want them at.
		while(village_x*village_y < tiles_per_village) {
			if(village_x < village_y) {
				++village_x;
			} else {
				++village_y;
			}
		}

		std::set<std::string> used_names;
		tcode_list_cache adj_liked_cache;

		config village_naming = game_config_.child("village_naming");

		if(cfg.has_child("village_naming")) {
			village_naming.append_attributes(cfg.child("village_naming"));
		}

		// If the [village_naming] child is empty, we cannot provide good names.
		std::map<map_location,std::string>* village_labels = village_naming.empty() ? nullptr : labels;

		for(int vx = 0; vx < data.width; vx += village_x) {
			LOG_NG << "village at " << vx << "\n";

			for(int vy = rng_()%village_y; vy < data.height; vy += village_y) {
				const size_t add = rng_()%3;
				const size_t x = (vx + add) - 1;
				const size_t y = (vy + add) - 1;

				const map_location res = place_village(terrain, x, y, 2, cfg, adj_liked_cache);

				if(res.x  < static_cast<long>(data.width     ) / 3 ||
				   res.x >= static_cast<long>(data.width  * 2) / 3 ||
				   res.y  < static_cast<long>(data.height    ) / 3 ||
				   res.y >= static_cast<long>(data.height * 2) / 3) {
					continue;
				}

				const std::string str = t_translation::write_terrain_code(terrain[res.x][res.y]);

				const std::string& convert_to = cfg.find_child("village", "terrain", str)["convert_to"].str();
				if(convert_to.empty()) {
					continue;
				}

				terrain[res.x][res.y] = t_translation::read_terrain_code(convert_to);

				villages.insert(res);

				if(village_labels == nullptr) {
					continue;
				}

				name_generator_factory village_name_generator_factory{ village_naming,
					{"base", "male", "village", "lake", "river", "bridge", "grassland", "forest", "hill", "mountain", "mountain_anon", "road", "swamp"} };

				village_naming.get_old_attribute("base_names", "male_names", "[village_naming]male_names= is deprecated, use base_names= instead");
				//Due to the attribute detection feature of the factory we also support male_name_generator= but keep it undocumented.

				base_name_generator = village_name_generator_factory.get_name_generator(
					(village_naming.has_attribute("base_names") || village_naming.has_attribute("base_name_generator")) ? "base" : "male" );

				const map_location loc(res.x-data.width/3,res.y-data.height/3);

				map_location adj[6];
				get_adjacent_tiles(loc,adj);

				std::string name_type = "village";
				const t_translation::ter_list
					field	 = t_translation::ter_list(1, t_translation::GRASS_LAND),
					forest   = t_translation::ter_list(1, t_translation::FOREST),
					mountain = t_translation::ter_list(1, t_translation::MOUNTAIN),
					hill	 = t_translation::ter_list(1, t_translation::HILL);

				size_t field_count = 0, forest_count = 0, mountain_count = 0, hill_count = 0;

				std::map<std::string,std::string> symbols;

				size_t n;
				for(n = 0; n != 6; ++n) {
					const std::map<map_location,std::string>::const_iterator road_name = road_names.find(adj[n]);
					if(road_name != road_names.end()) {
						symbols["road"] = road_name->second;
						name_type = "road";
						break;
					}

					const std::map<map_location,std::string>::const_iterator river_name = river_names.find(adj[n]);
					if(river_name != river_names.end()) {
						symbols["river"] = river_name->second;
						name_type = "river";

						const std::map<map_location,std::string>::const_iterator bridge_name = bridge_names.find(adj[n]);
						if(bridge_name != bridge_names.end()) {
							//we should always end up here, since if there is an adjacent bridge, there has to be an adjacent river too
							symbols["bridge"] = bridge_name->second;
							name_type = "river_bridge";
						}

						break;
					}

					const std::map<map_location,std::string>::const_iterator forest_name = forest_names.find(adj[n]);
					if(forest_name != forest_names.end()) {
						symbols["forest"] = forest_name->second;
						name_type = "forest";
						break;
					}

					const std::map<map_location,std::string>::const_iterator lake_name = lake_names.find(adj[n]);
					if(lake_name != lake_names.end()) {
						symbols["lake"] = lake_name->second;
						name_type = "lake";
						break;
					}

					const std::map<map_location,std::string>::const_iterator mountain_name = mountain_names.find(adj[n]);
					if(mountain_name != mountain_names.end()) {
						symbols["mountain"] = mountain_name->second;
						name_type = "mountain";
						break;
					}

					const std::map<map_location,std::string>::const_iterator swamp_name = swamp_names.find(adj[n]);
					if(swamp_name != swamp_names.end()) {
						symbols["swamp"] = swamp_name->second;
						name_type = "swamp";
						break;
					}

					const t_translation::terrain_code terr = terrain[adj[n].x+data.width/3][adj[n].y+data.height/3];

					if(std::count(field.begin(),field.end(),terr) > 0) {
						++field_count;
					} else if(std::count(forest.begin(),forest.end(),terr) > 0) {
						++forest_count;
					} else if(std::count(hill.begin(),hill.end(),terr) > 0) {
						++hill_count;
					} else if(std::count(mountain.begin(),mountain.end(),terr) > 0) {
						++mountain_count;
					}
				}

				if(n == 6) {
					if(field_count == 6) {
						name_type = "grassland";
					} else if(forest_count >= 2) {
						name_type = "forest";
					} else if(mountain_count >= 1) {
						name_type = "mountain_anon";
					} else if(hill_count >= 2) {
						name_type = "hill";
					}
				}

				std::string name;

				symbols["base"] = base_name_generator->generate();
				std::shared_ptr<name_generator> village_name_generator = village_name_generator_factory.get_name_generator(name_type);

				for(size_t ntry = 0; ntry != 30 && (ntry == 0 || used_names.count(name) > 0); ++ntry) {
					name = village_name_generator->generate(symbols);
				}

				used_names.insert(name);
				village_labels->emplace(loc, name);
			}
		}
	}

	LOG_NG << "Placed villages. " << (SDL_GetTicks() - ticks) << " ticks elapsed" << "\n";

	return output_map(terrain, starting_positions);
}