1 /*
2 Copyright (C) 2008 - 2018 by Tomasz Sniatowski <kailoran@gmail.com>
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 #define GETTEXT_DOMAIN "wesnoth-editor"
15
16 #include "editor/action/action_base.hpp"
17 #include "editor/map/editor_map.hpp"
18
19 #include "display.hpp"
20 #include "formula/string_utils.hpp"
21 #include "gettext.hpp"
22 #include "map/exception.hpp"
23 #include "map/label.hpp"
24 #include "wml_exception.hpp"
25
26 #include "terrain/type_data.hpp"
27
28 namespace editor {
29
wrap_exc(const char * type,const std::string & e_msg,const std::string & filename)30 editor_map_load_exception wrap_exc(const char* type, const std::string& e_msg, const std::string& filename)
31 {
32 WRN_ED << type << " error in load map " << filename << ": " << e_msg << std::endl;
33 utils::string_map symbols;
34 symbols["type"] = type;
35 const char* error_msg = "There was an error ($type) while loading the file:";
36 std::string msg = VGETTEXT(error_msg, symbols);
37 msg += "\n";
38 msg += e_msg;
39 return editor_map_load_exception(filename, msg);
40 }
41
editor_map(const config & terrain_cfg)42 editor_map::editor_map(const config& terrain_cfg)
43 : gamemap(std::make_shared<terrain_type_data>(terrain_cfg), "")
44 , selection_()
45 {
46 }
47
editor_map(const config & terrain_cfg,const std::string & data)48 editor_map::editor_map(const config& terrain_cfg, const std::string& data)
49 : gamemap(std::make_shared<terrain_type_data>(terrain_cfg), data)
50 , selection_()
51 {
52 sanity_check();
53 }
54
from_string(const config & terrain_cfg,const std::string & data)55 editor_map editor_map::from_string(const config& terrain_cfg, const std::string& data)
56 {
57 try {
58 return editor_map(terrain_cfg, data);
59 } catch (const incorrect_map_format_error& e) {
60 throw wrap_exc("format", e.message, "");
61 } catch (const wml_exception& e) {
62 throw wrap_exc("wml", e.user_message, "");
63 } catch (const config::error& e) {
64 throw wrap_exc("config", e.message, "");
65 }
66 }
67
editor_map(const config & terrain_cfg,size_t width,size_t height,const t_translation::terrain_code & filler)68 editor_map::editor_map(const config& terrain_cfg, size_t width, size_t height, const t_translation::terrain_code & filler)
69 : gamemap(std::make_shared<terrain_type_data>(terrain_cfg), t_translation::write_game_map(t_translation::ter_map(width + 2, height + 2, filler)))
70 , selection_()
71 {
72 sanity_check();
73 }
74
editor_map(const gamemap & map)75 editor_map::editor_map(const gamemap& map)
76 : gamemap(map)
77 , selection_()
78 {
79 sanity_check();
80 }
81
~editor_map()82 editor_map::~editor_map()
83 {
84 }
85
sanity_check()86 void editor_map::sanity_check()
87 {
88 int errors = 0;
89 if (total_width() != tiles_.w) {
90 ERR_ED << "total_width is " << total_width() << " but tiles_.size() is " << tiles_.w << std::endl;
91 ++errors;
92 }
93 if (total_height() != tiles_.h) {
94 ERR_ED << "total_height is " << total_height() << " but tiles_[0].size() is " << tiles_.h << std::endl;
95 ++errors;
96 }
97 if (w() + 2 * border_size() != total_width()) {
98 ERR_ED << "h is " << h_ << " and border_size is " << border_size() << " but total_width is " << total_width() << std::endl;
99 ++errors;
100 }
101 if (h() + 2 * border_size() != total_height()) {
102 ERR_ED << "w is " << w_ << " and border_size is " << border_size() << " but total_height is " << total_height() << std::endl;
103 ++errors;
104 }
105 for (const map_location& loc : selection_) {
106 if (!on_board_with_border(loc)) {
107 ERR_ED << "Off-map tile in selection: " << loc << std::endl;
108 }
109 }
110 if (errors) {
111 throw editor_map_integrity_error();
112 }
113 }
114
get_contiguous_terrain_tiles(const map_location & start) const115 std::set<map_location> editor_map::get_contiguous_terrain_tiles(const map_location& start) const
116 {
117 t_translation::terrain_code terrain = get_terrain(start);
118 std::set<map_location> result;
119 std::deque<map_location> queue;
120 result.insert(start);
121 queue.push_back(start);
122 //this is basically a breadth-first search along adjacent hexes
123 do {
124 adjacent_loc_array_t adj;
125 get_adjacent_tiles(queue.front(), adj.data());
126 for (unsigned i = 0; i < adj.size(); ++i) {
127 if (on_board_with_border(adj[i]) && get_terrain(adj[i]) == terrain
128 && result.find(adj[i]) == result.end()) {
129 result.insert(adj[i]);
130 queue.push_back(adj[i]);
131 }
132 }
133 queue.pop_front();
134 } while (!queue.empty());
135 return result;
136 }
137
set_starting_position_labels(display & disp)138 std::set<map_location> editor_map::set_starting_position_labels(display& disp)
139 {
140 std::set<map_location> label_locs;
141 std::string label;
142
143
144 for (const auto& pair : starting_positions_.left) {
145
146 bool is_number = std::find_if(pair.first.begin(), pair.first.end(), [](char c) { return !std::isdigit(c); }) == pair.first.end();
147 if (is_number) {
148 label = VGETTEXT("Player $side_num", utils::string_map{ { "side_num", pair.first } });
149 }
150 else {
151 label = pair.first;
152 }
153
154 disp.labels().set_label(pair.second, label);
155 label_locs.insert(pair.second);
156 }
157 return label_locs;
158 }
159
in_selection(const map_location & loc) const160 bool editor_map::in_selection(const map_location& loc) const
161 {
162 return selection_.find(loc) != selection_.end();
163 }
164
add_to_selection(const map_location & loc)165 bool editor_map::add_to_selection(const map_location& loc)
166 {
167 return on_board_with_border(loc) ? selection_.insert(loc).second : false;
168 }
169
set_selection(const std::set<map_location> & area)170 bool editor_map::set_selection(const std::set<map_location>& area)
171 {
172 clear_selection();
173 for (const map_location& loc : area) {
174 if (!add_to_selection(loc))
175 return false;
176 }
177 return true;
178 }
179
remove_from_selection(const map_location & loc)180 bool editor_map::remove_from_selection(const map_location& loc)
181 {
182 return selection_.erase(loc) != 0;
183 }
184
clear_selection()185 void editor_map::clear_selection()
186 {
187 selection_.clear();
188 }
189
invert_selection()190 void editor_map::invert_selection()
191 {
192 std::set<map_location> new_selection;
193 for (int x = -1; x < w() + 1; ++x) {
194 for (int y = -1; y < h() + 1; ++y) {
195 if (selection_.find(map_location(x, y)) == selection_.end()) {
196 new_selection.emplace(x, y);
197 }
198 }
199 }
200 selection_.swap(new_selection);
201 }
202
select_all()203 void editor_map::select_all()
204 {
205 clear_selection();
206 invert_selection();
207 }
208
everything_selected() const209 bool editor_map::everything_selected() const
210 {
211 LOG_ED << selection_.size() << " " << total_width() * total_height() << "\n";
212 return static_cast<int>(selection_.size()) == total_width() * total_height();
213 }
214
resize(int width,int height,int x_offset,int y_offset,const t_translation::terrain_code & filler)215 void editor_map::resize(int width, int height, int x_offset, int y_offset,
216 const t_translation::terrain_code & filler)
217 {
218 int old_w = w();
219 int old_h = h();
220 if (old_w == width && old_h == height && x_offset == 0 && y_offset == 0) {
221 return;
222 }
223
224 // Determine the amount of resizing is required
225 const int left_resize = -x_offset;
226 const int right_resize = (width - old_w) + x_offset;
227 const int top_resize = -y_offset;
228 const int bottom_resize = (height - old_h) + y_offset;
229
230 if(right_resize > 0) {
231 expand_right(right_resize, filler);
232 } else if(right_resize < 0) {
233 shrink_right(-right_resize);
234 }
235 if(bottom_resize > 0) {
236 expand_bottom(bottom_resize, filler);
237 } else if(bottom_resize < 0) {
238 shrink_bottom(-bottom_resize);
239 }
240 if(left_resize > 0) {
241 expand_left(left_resize, filler);
242 } else if(left_resize < 0) {
243 shrink_left(-left_resize);
244 }
245 if(top_resize > 0) {
246 expand_top(top_resize, filler);
247 } else if(top_resize < 0) {
248 shrink_top(-top_resize);
249 }
250
251 // fix the starting positions
252 if(x_offset || y_offset) {
253 for (auto it = starting_positions_.left.begin(); it != starting_positions_.left.end(); ++it) {
254 starting_positions_.left.modify_data(it, [=](t_translation::coordinate & loc) { loc.add(-x_offset, -y_offset); });
255 }
256 }
257
258 villages_.clear();
259
260 //
261 // NOTE: I'm not sure how inefficient it is to check every loc for its village-ness as
262 // opposed to operating on the villages_ vector itself and figuring out how to handle
263 // villages on the map border. Essentially, it's possible to simply remove all members
264 // from villages_ that are no longer on the map after a resize (including those that
265 // land on a border), but that doesn't account for villages that were *on* the border
266 // prior to resizing. Those should be included. As a catch-all fix, I just check every
267 // hex. It's possible that any more complex shenanigans would be even more inefficient.
268 //
269 // -- vultraz, 2018-02-25
270 //
271 for_each_loc([this](const map_location& loc) {
272 if(is_village(loc)) {
273 villages_.push_back(loc);
274 }
275 });
276
277 sanity_check();
278 }
279
mask_to(const gamemap & target) const280 gamemap editor_map::mask_to(const gamemap& target) const
281 {
282 if (target.w() != w() || target.h() != h()) {
283 throw editor_action_exception(_("The size of the target map is different from the current map"));
284 }
285 gamemap mask(target);
286 map_location iter;
287 for (iter.x = -border_size(); iter.x < w() + border_size(); ++iter.x) {
288 for (iter.y = -border_size(); iter.y < h() + border_size(); ++iter.y) {
289 if (target.get_terrain(iter) == get_terrain(iter)) {
290 mask.set_terrain(iter, t_translation::FOGGED);
291 }
292 }
293 }
294 return mask;
295 }
296
same_size_as(const gamemap & other) const297 bool editor_map::same_size_as(const gamemap& other) const
298 {
299 return h() == other.h()
300 && w() == other.w();
301 }
302
expand_right(int count,const t_translation::terrain_code & filler)303 void editor_map::expand_right(int count, const t_translation::terrain_code & filler)
304 {
305 t_translation::ter_map tiles_new(tiles_.w + count, tiles_.h);
306 w_ += count;
307 for (int x = 0, x_end = tiles_.w; x != x_end; ++x) {
308 for (int y = 0, y_end = tiles_.h; y != y_end; ++y) {
309 tiles_new.get(x, y) = tiles_.get(x, y);
310 }
311 }
312 for (int x = tiles_.w, x_end = tiles_.w + count; x != x_end; ++x) {
313 for (int y = 0, y_end = tiles_.h; y != y_end; ++y) {
314 tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(tiles_.w - 1, y) : filler;
315 }
316 }
317 tiles_ = std::move(tiles_new);
318 }
319
expand_left(int count,const t_translation::terrain_code & filler)320 void editor_map::expand_left(int count, const t_translation::terrain_code & filler)
321 {
322 t_translation::ter_map tiles_new(tiles_.w + count, tiles_.h);
323 w_ += count;
324 for (int x = 0, x_end = tiles_.w; x != x_end; ++x) {
325 for (int y = 0, y_end = tiles_.h; y != y_end; ++y) {
326 tiles_new.get(x + count, y) = tiles_.get(x, y);
327 }
328 }
329 for (int x = 0, x_end = count; x != x_end; ++x) {
330 for (int y = 0, y_end = tiles_.h; y != y_end; ++y) {
331 tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(0, y) : filler;
332 }
333 }
334 tiles_ = std::move(tiles_new);
335 }
336
expand_top(int count,const t_translation::terrain_code & filler)337 void editor_map::expand_top(int count, const t_translation::terrain_code & filler)
338 {
339 t_translation::ter_map tiles_new(tiles_.w, tiles_.h + count);
340 h_ += count;
341 for (int x = 0, x_end = tiles_.w; x != x_end; ++x) {
342 for (int y = 0, y_end = tiles_.h; y != y_end; ++y) {
343 tiles_new.get(x, y + count) = tiles_.get(x, y);
344 }
345 }
346 for (int x = 0, x_end = tiles_.w; x != x_end; ++x) {
347 for (int y = 0, y_end = count; y != y_end; ++y) {
348 tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(x, 0) : filler;
349 }
350 }
351 tiles_ = std::move(tiles_new);
352 }
353
expand_bottom(int count,const t_translation::terrain_code & filler)354 void editor_map::expand_bottom(int count, const t_translation::terrain_code & filler)
355 {
356 t_translation::ter_map tiles_new(tiles_.w, tiles_.h + count);
357 h_ += count;
358 for (int x = 0, x_end = tiles_.w; x != x_end; ++x) {
359 for (int y = 0, y_end = tiles_.h; y != y_end; ++y) {
360 tiles_new.get(x, y) = tiles_.get(x, y);
361 }
362 }
363 for (int x = 0, x_end = tiles_.w; x != x_end; ++x) {
364 for (int y = tiles_.h, y_end = tiles_.h + count; y != y_end; ++y) {
365 tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles_.get(x, tiles_.h - 1) : filler;
366 }
367 }
368 tiles_ = std::move(tiles_new);
369 }
370
shrink_right(int count)371 void editor_map::shrink_right(int count)
372 {
373 if(count < 0 || count > tiles_.w) {
374 throw editor_map_operation_exception();
375 }
376 t_translation::ter_map tiles_new(tiles_.w - count, tiles_.h);
377 for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
378 for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
379 tiles_new.get(x, y) = tiles_.get(x, y);
380 }
381 }
382 w_ -= count;
383 tiles_ = std::move(tiles_new);
384 }
385
shrink_left(int count)386 void editor_map::shrink_left(int count)
387 {
388 if (count < 0 || count > tiles_.w) {
389 throw editor_map_operation_exception();
390 }
391 t_translation::ter_map tiles_new(tiles_.w - count, tiles_.h);
392 for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
393 for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
394 tiles_new.get(x, y) = tiles_.get(x + count, y);
395 }
396 }
397 w_ -= count;
398 tiles_ = std::move(tiles_new);
399 }
400
shrink_top(int count)401 void editor_map::shrink_top(int count)
402 {
403 if (count < 0 || count > tiles_.h) {
404 throw editor_map_operation_exception();
405 }
406 t_translation::ter_map tiles_new(tiles_.w, tiles_.h - count);
407 for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
408 for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
409 tiles_new.get(x, y) = tiles_.get(x, y + count);
410 }
411 }
412 h_ -= count;
413 tiles_ = std::move(tiles_new);
414 }
415
shrink_bottom(int count)416 void editor_map::shrink_bottom(int count)
417 {
418 if (count < 0 || count > tiles_.h) {
419 throw editor_map_operation_exception();
420 }
421 t_translation::ter_map tiles_new(tiles_.w, tiles_.h - count);
422 for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
423 for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
424 tiles_new.get(x, y) = tiles_.get(x, y);
425 }
426 }
427 h_ -= count;
428 tiles_ = std::move(tiles_new);
429 }
430
431
432
433 } //end namespace editor
434