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