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