1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3    Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY.
11 
12    See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "generators/default_map_generator.hpp"
18 
19 #include "gui/dialogs/editor/generator_settings.hpp"
20 #include "generators/default_map_generator_job.hpp"
21 #include "gettext.hpp"
22 #include "log.hpp"
23 #include "map/map.hpp"
24 #include "seed_rng.hpp"
25 
26 static lg::log_domain log_engine("engine");
27 #define DBG_NG LOG_STREAM(debug, log_engine)
28 
29 namespace {
30 	const int max_island = 10;
31 	const int max_coastal = 5;
32 }
33 
generator_data(const config & cfg)34 generator_data::generator_data(const config &cfg)
35 	: width(std::max(0, cfg["map_width"].to_int(40)))
36 	, height(std::max(0, cfg["map_height"].to_int(40)))
37 	, default_width(width)
38 	, default_height(height)
39 	, nplayers(std::max(0, cfg["players"].to_int(2)))
40 	, nvillages(std::max(0, cfg["villages"].to_int(25)))
41 	, iterations(std::max(0, cfg["iterations"].to_int(1000)))
42 	, hill_size(std::max(0, cfg["hill_size"].to_int(10)))
43 	, castle_size(std::max(0, cfg["castle_size"].to_int(9)))
44 	, island_size(std::max(0, cfg["island_size"].to_int(0)))
45 	, island_off_center(0)
46 	, max_lakes(std::max(0, cfg["max_lakes"].to_int(20)))
47 	, link_castles(true)
48 	, show_labels(true)
49 {
50 }
51 
default_map_generator(const config & cfg)52 default_map_generator::default_map_generator(const config& cfg)
53 	: cfg_(cfg)
54 	, data_(cfg)
55 {
56 }
57 
allow_user_config() const58 bool default_map_generator::allow_user_config() const { return true; }
59 
user_config()60 void default_map_generator::user_config()
61 {
62 	gui2::dialogs::generator_settings::execute(data_);
63 }
64 
name() const65 std::string default_map_generator::name() const { return "default"; }
66 
config_name() const67 std::string default_map_generator::config_name() const
68 {
69 	if (const config &c = cfg_.child("scenario"))
70 		return c["name"];
71 
72 	return std::string();
73 }
74 
create_map(boost::optional<uint32_t> randomseed)75 std::string default_map_generator::create_map(boost::optional<uint32_t> randomseed)
76 {
77 	return generate_map(nullptr, randomseed);
78 }
79 
generate_map(std::map<map_location,std::string> * labels,boost::optional<uint32_t> randomseed)80 std::string default_map_generator::generate_map(std::map<map_location,std::string>* labels, boost::optional<uint32_t> randomseed)
81 {
82 	uint32_t seed;
83 	if(const uint32_t* pseed = randomseed.get_ptr()) {
84 		seed = *pseed;
85 	} else {
86 		seed = seed_rng::next_seed();
87 	}
88 
89 	/* We construct a copy of the generator data and modify it as needed. This ensures every time
90 	 * this function is called the generator job gets a fresh set of settings, and that the internal
91 	 * copy of the settings are never touched except by the settings dialog.
92 	 *
93 	 * The original data is still used for conditional checks and calculations, but any modifications
94 	 * should be done on this object.
95 	 */
96 	generator_data job_data = data_;
97 
98 	// Suppress labels?
99 	if(!data_.show_labels) {
100 		labels = nullptr;
101 	}
102 
103 	// The random generator thinks odd widths are nasty, so make them even
104 	if(is_odd(data_.width)) {
105 		++job_data.width;
106 	}
107 
108 	job_data.iterations = (data_.iterations * data_.width * data_.height)/(data_.default_width * data_.default_height);
109 	job_data.island_size = 0;
110 	job_data.nvillages = (data_.nvillages * data_.width * data_.height) / 1000;
111 	job_data.island_off_center = 0;
112 
113 	if(data_.island_size >= max_coastal) {
114 		// Islands look good with much fewer iterations than normal, and fewer lakes
115 		job_data.iterations /= 10;
116 		job_data.max_lakes /= 9;
117 
118 		// The radius of the island should be up to half the width of the map
119 		const int island_radius = 50 + ((max_island - data_.island_size) * 50)/(max_island - max_coastal);
120 		job_data.island_size = (island_radius * (data_.width/2))/100;
121 	} else if(data_.island_size > 0) {
122 		// The radius of the island should be up to twice the width of the map
123 		const int island_radius = 40 + ((max_coastal - data_.island_size) * 40)/max_coastal;
124 		job_data.island_size = (island_radius * data_.width * 2)/100;
125 		job_data.island_off_center = std::min(data_.width, data_.height);
126 		DBG_NG << "calculated coastal params...\n";
127 	}
128 
129 	// A map generator can fail so try a few times to get a map before aborting.
130 	std::string map;
131 
132 	// Keep a copy of labels as it can be written to by the map generator func
133 	std::map<map_location,std::string> labels_copy;
134 	std::map<map_location,std::string>* labels_ptr = labels ? &labels_copy : nullptr;
135 
136 	// Iinitilize the job outside the loop so that we really get a different result every time we run the loop.
137 	default_map_generator_job job(seed);
138 
139 	int tries = 10;
140 	std::string error_message;
141 	do {
142 		// Reset the labels.
143 		if(labels) {
144 			labels_copy = *labels;
145 		}
146 
147 		try {
148 			map = job.default_generate_map(job_data, labels_ptr, cfg_);
149 			error_message = "";
150 		} catch(const mapgen_exception& exc) {
151 			error_message = exc.message;
152 		}
153 
154 		--tries;
155 	} while(tries && map.empty());
156 
157 	if(labels) {
158 		labels->swap(labels_copy);
159 	}
160 
161 	if(!error_message.empty()) {
162 		throw mapgen_exception(error_message);
163 	}
164 
165 	return map;
166 }
167 
create_scenario(boost::optional<uint32_t> randomseed)168 config default_map_generator::create_scenario(boost::optional<uint32_t> randomseed)
169 {
170 	DBG_NG << "creating scenario...\n";
171 
172 	config res = cfg_.child_or_empty("scenario");
173 
174 	DBG_NG << "got scenario data...\n";
175 
176 	std::map<map_location,std::string> labels;
177 	DBG_NG << "generating map...\n";
178 
179 	try{
180 		res["map_data"] = generate_map(&labels, randomseed);
181 	}
182 	catch (const mapgen_exception& exc){
183 		res["map_data"] = "";
184 		res["error_message"] = exc.message;
185 	}
186 	DBG_NG << "done generating map..\n";
187 
188 	for(std::map<map_location,std::string>::const_iterator i =
189 			labels.begin(); i != labels.end(); ++i) {
190 
191 		if(i->first.x >= 0 && i->first.y >= 0 &&
192 				i->first.x < static_cast<long>(data_.width) &&
193 				i->first.y < static_cast<long>(data_.height)) {
194 
195 			config& label = res.add_child("label");
196 			label["text"] = i->second;
197 			label["category"] = _("Villages");
198 			i->first.write(label);
199 		}
200 	}
201 
202 	return res;
203 }
204