The Battle for Wesnoth  1.19.4+dev
editor_map.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
3  by Tomasz Sniatowski <kailoran@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-editor"
17 
20 
21 #include "display.hpp"
22 #include "formula/string_utils.hpp"
23 #include "gettext.hpp"
24 #include "map/exception.hpp"
25 #include "map/label.hpp"
26 #include "wml_exception.hpp"
27 
28 namespace editor {
29 
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;
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;
40 }
41 
43  : gamemap("")
44  , selection_()
45 {
46 }
47 
48 editor_map::editor_map(const std::string& data)
49  : gamemap(data)
50  , selection_()
51 {
52  sanity_check();
53 }
54 
56 {
57  try {
58  return editor_map(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 
68 editor_map::editor_map(std::size_t width, std::size_t height, const t_translation::terrain_code & filler)
69  : gamemap(t_translation::write_game_map(t_translation::ter_map(width + 2, height + 2, filler)))
70  , selection_()
71 {
72  sanity_check();
73 }
74 
76  : gamemap(map)
77  , selection_()
78 {
79  sanity_check();
80 }
81 
83 {
84 }
85 
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;
91  ++errors;
92  }
93  if (total_height() != tiles().h) {
94  ERR_ED << "total_height is " << total_height() << " but tiles()[0].size() is " << tiles().h;
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();
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();
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;
108  }
109  }
110  if (errors) {
112  }
113 }
114 
115 std::set<map_location> editor_map::get_contiguous_terrain_tiles(const map_location& start) const
116 {
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  for(const map_location& adj : get_adjacent_tiles(queue.front())) {
125  if (on_board_with_border(adj) && get_terrain(adj) == terrain
126  && result.find(adj) == result.end()) {
127  result.insert(adj);
128  queue.push_back(adj);
129  }
130  }
131  queue.pop_front();
132  } while (!queue.empty());
133  return result;
134 }
135 
137 {
138  std::set<map_location> label_locs;
139  std::string label;
140 
141 
142  for (const auto& pair : special_locations().left) {
143 
144  bool is_number = std::find_if(pair.first.begin(), pair.first.end(), [](char c) { return !std::isdigit(c); }) == pair.first.end();
145  if (is_number) {
146  label = VGETTEXT("Player $side_num", utils::string_map{ { "side_num", pair.first } });
147  }
148  else {
149  label = pair.first;
150  }
151 
152  disp.labels().set_label(pair.second, label);
153  label_locs.insert(pair.second);
154  }
155  return label_locs;
156 }
157 
159 {
160  return selection_.find(loc) != selection_.end();
161 }
162 
164 {
165  return on_board_with_border(loc) ? selection_.insert(loc).second : false;
166 }
167 
168 bool editor_map::set_selection(const std::set<map_location>& area)
169 {
170  clear_selection();
171  for (const map_location& loc : area) {
172  if (!add_to_selection(loc))
173  return false;
174  }
175  return true;
176 }
177 
179 {
180  return selection_.erase(loc) != 0;
181 }
182 
184 {
185  selection_.clear();
186 }
187 
189 {
190  std::set<map_location> new_selection;
191  for (int x = -1; x < w() + 1; ++x) {
192  for (int y = -1; y < h() + 1; ++y) {
193  if (selection_.find(map_location(x, y)) == selection_.end()) {
194  new_selection.emplace(x, y);
195  }
196  }
197  }
198  selection_.swap(new_selection);
199 }
200 
202 {
203  clear_selection();
205 }
206 
208 {
209  LOG_ED << selection_.size() << " " << total_width() * total_height();
210  return static_cast<int>(selection_.size()) == total_width() * total_height();
211 }
212 
213 void editor_map::resize(int width, int height, int x_offset, int y_offset,
214  const t_translation::terrain_code & filler)
215 {
216  int old_w = w();
217  int old_h = h();
218  if (old_w == width && old_h == height && x_offset == 0 && y_offset == 0) {
219  return;
220  }
221 
222  // Determine the amount of resizing is required
223  const int left_resize = -x_offset;
224  const int right_resize = (width - old_w) + x_offset;
225  const int top_resize = -y_offset;
226  const int bottom_resize = (height - old_h) + y_offset;
227 
228  if(right_resize > 0) {
229  expand_right(right_resize, filler);
230  } else if(right_resize < 0) {
231  shrink_right(-right_resize);
232  }
233  if(bottom_resize > 0) {
234  expand_bottom(bottom_resize, filler);
235  } else if(bottom_resize < 0) {
236  shrink_bottom(-bottom_resize);
237  }
238  if(left_resize > 0) {
239  expand_left(left_resize, filler);
240  } else if(left_resize < 0) {
241  shrink_left(-left_resize);
242  }
243  if(top_resize > 0) {
244  expand_top(top_resize, filler);
245  } else if(top_resize < 0) {
246  shrink_top(-top_resize);
247  }
248 
249  // fix the starting positions
250  if(x_offset || y_offset) {
251  for (auto it = special_locations().left.begin(); it != special_locations().left.end(); ++it) {
252  special_locations().left.modify_data(it, [=](t_translation::coordinate & loc) { loc.add(-x_offset, -y_offset); });
253  }
254  }
255 
256  villages_.clear();
257 
258  //
259  // NOTE: I'm not sure how inefficient it is to check every loc for its village-ness as
260  // opposed to operating on the villages_ vector itself and figuring out how to handle
261  // villages on the map border. Essentially, it's possible to simply remove all members
262  // from villages_ that are no longer on the map after a resize (including those that
263  // land on a border), but that doesn't account for villages that were *on* the border
264  // prior to resizing. Those should be included. As a catch-all fix, I just check every
265  // hex. It's possible that any more complex shenanigans would be even more inefficient.
266  //
267  // -- vultraz, 2018-02-25
268  //
269  for_each_loc([this](const map_location& loc) {
270  if(is_village(loc)) {
271  villages_.push_back(loc);
272  }
273  });
274 
275  sanity_check();
276 }
277 
278 gamemap editor_map::mask_to(const gamemap& target) const
279 {
280  if (target.w() != w() || target.h() != h()) {
281  throw editor_action_exception(_("The size of the target map is different from the current map"));
282  }
283  gamemap mask(target);
284  map_location iter;
285  for (iter.x = -border_size(); iter.x < w() + border_size(); ++iter.x) {
286  for (iter.y = -border_size(); iter.y < h() + border_size(); ++iter.y) {
287  if (target.get_terrain(iter) == get_terrain(iter)) {
289  }
290  }
291  }
292  return mask;
293 }
294 
295 bool editor_map::same_size_as(const gamemap& other) const
296 {
297  return h() == other.h()
298  && w() == other.w();
299 }
300 
302 {
303  t_translation::ter_map tiles_new(tiles().w + count, tiles().h);
304  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
305  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
306  tiles_new.get(x, y) = tiles().get(x, y);
307  }
308  }
309  for (int x = tiles().w, x_end = tiles().w + count; x != x_end; ++x) {
310  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
311  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(tiles().w - 1, y) : filler;
312  }
313  }
314  tiles() = std::move(tiles_new);
315 }
316 
318 {
319  t_translation::ter_map tiles_new(tiles().w + count, tiles().h);
320  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
321  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
322  tiles_new.get(x + count, y) = tiles().get(x, y);
323  }
324  }
325  for (int x = 0, x_end = count; x != x_end; ++x) {
326  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
327  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(0, y) : filler;
328  }
329  }
330  tiles() = std::move(tiles_new);
331 }
332 
334 {
335  t_translation::ter_map tiles_new(tiles().w, tiles().h + count);
336  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
337  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
338  tiles_new.get(x, y + count) = tiles().get(x, y);
339  }
340  }
341  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
342  for (int y = 0, y_end = count; y != y_end; ++y) {
343  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(x, 0) : filler;
344  }
345  }
346  tiles() = std::move(tiles_new);
347 }
348 
350 {
351  t_translation::ter_map tiles_new(tiles().w, tiles().h + count);
352  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
353  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
354  tiles_new.get(x, y) = tiles().get(x, y);
355  }
356  }
357  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
358  for (int y = tiles().h, y_end = tiles().h + count; y != y_end; ++y) {
359  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(x, tiles().h - 1) : filler;
360  }
361  }
362  tiles() = std::move(tiles_new);
363 }
364 
366 {
367  if(count < 0 || count > tiles().w) {
369  }
370  t_translation::ter_map tiles_new(tiles().w - count, tiles().h);
371  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
372  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
373  tiles_new.get(x, y) = tiles().get(x, y);
374  }
375  }
376  tiles() = std::move(tiles_new);
377 }
378 
379 void editor_map::shrink_left(int count)
380 {
381  if (count < 0 || count > tiles().w) {
383  }
384  t_translation::ter_map tiles_new(tiles().w - count, tiles().h);
385  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
386  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
387  tiles_new.get(x, y) = tiles().get(x + count, y);
388  }
389  }
390  tiles() = std::move(tiles_new);
391 }
392 
393 void editor_map::shrink_top(int count)
394 {
395  if (count < 0 || count > tiles().h) {
397  }
398  t_translation::ter_map tiles_new(tiles().w, tiles().h - count);
399  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
400  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
401  tiles_new.get(x, y) = tiles().get(x, y + count);
402  }
403  }
404  tiles() = std::move(tiles_new);
405 }
406 
408 {
409  if (count < 0 || count > tiles().h) {
411  }
412  t_translation::ter_map tiles_new(tiles().w, tiles().h - count);
413  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
414  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
415  tiles_new.get(x, y) = tiles().get(x, y);
416  }
417  }
418  tiles() = std::move(tiles_new);
419 }
420 
421 
422 
423 } //end namespace editor
Base class for editor actions.
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:89
map_labels & labels()
Definition: display.cpp:2598
This class adds extra editor-specific functionality to a normal gamemap.
Definition: editor_map.hpp:70
void sanity_check()
Debugging aid.
Definition: editor_map.cpp:86
void shrink_left(int count)
Definition: editor_map.cpp:379
bool everything_selected() const
Definition: editor_map.cpp:207
bool add_to_selection(const map_location &loc)
Add a location to the selection.
Definition: editor_map.cpp:163
std::set< map_location > selection_
The selected hexes.
Definition: editor_map.hpp:203
bool set_selection(const std::set< map_location > &area)
Select the given area.
Definition: editor_map.cpp:168
~editor_map()
editor_map destructor
Definition: editor_map.cpp:82
void shrink_top(int count)
Definition: editor_map.cpp:393
void expand_left(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:317
std::set< map_location > set_starting_position_labels(display &disp)
Set labels for staring positions in the given display object.
Definition: editor_map.cpp:136
void shrink_bottom(int count)
Definition: editor_map.cpp:407
void invert_selection()
Invert the selection, i.e.
Definition: editor_map.cpp:188
void shrink_right(int count)
Definition: editor_map.cpp:365
void select_all()
Select all map hexes.
Definition: editor_map.cpp:201
bool same_size_as(const gamemap &other) const
A precondition to several map operations.
Definition: editor_map.cpp:295
gamemap mask_to(const gamemap &target) const
A sort-of diff operation returning a mask that, when applied to the current editor_map,...
Definition: editor_map.cpp:278
void expand_right(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:301
bool in_selection(const map_location &loc) const
Definition: editor_map.cpp:158
bool remove_from_selection(const map_location &loc)
Remove a location to the selection.
Definition: editor_map.cpp:178
void expand_top(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:333
static editor_map from_string(const std::string &data)
Wrapper around editor_map(cfg, data) that catches possible exceptions and wraps them in a editor_map_...
Definition: editor_map.cpp:55
std::set< map_location > get_contiguous_terrain_tiles(const map_location &start) const
Get a contiguous set of tiles having the same terrain as the starting location.
Definition: editor_map.cpp:115
void resize(int width, int height, int x_offset, int y_offset, const t_translation::terrain_code &filler=t_translation::NONE_TERRAIN)
Resize the map.
Definition: editor_map.cpp:213
editor_map()
Empty map constructor.
Definition: editor_map.cpp:42
void clear_selection()
Clear the selection.
Definition: editor_map.cpp:183
void expand_bottom(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:349
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:301
int w() const
Effective map width.
Definition: map.hpp:50
terrain_map & tiles()
Definition: map.hpp:157
int h() const
Effective map height.
Definition: map.hpp:53
void for_each_loc(const F &f) const
Definition: map.hpp:136
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:59
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:389
int total_height() const
Real height of the map, including borders.
Definition: map.hpp:62
int border_size() const
Size of the map border.
Definition: map.hpp:56
location_map & special_locations()
Definition: map.hpp:90
Encapsulates the map of the game.
Definition: map.hpp:172
bool is_village(const map_location &loc) const
Definition: map.cpp:65
std::vector< map_location > villages_
Definition: map.hpp:257
void set_terrain(const map_location &loc, const terrain_code &terrain, const terrain_type_data::merge_mode mode=terrain_type_data::BOTH, bool replace_if_failed=false) override
Clobbers over the terrain at location 'loc', with the given terrain.
Definition: map.cpp:396
const terrain_label * set_label(const map_location &loc, const t_string &text, const int creator=-1, const std::string &team="", const color_t color=font::NORMAL_COLOR, const bool visible_in_fog=true, const bool visible_in_shroud=false, const bool immutable=false, const std::string &category="", const t_string &tooltip="")
Definition: label.cpp:147
map_display and display: classes which take care of displaying the map and game-data on the screen.
#define LOG_ED
#define ERR_ED
#define WRN_ED
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:199
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:465
Manage the empty-palette in the editor.
Definition: action.cpp:31
editor_map_load_exception wrap_exc(const char *type, const std::string &e_msg, const std::string &filename)
Exception wrapping utility.
Definition: editor_map.cpp:30
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
std::string write_game_map(const ter_map &map, const starting_positions &starting_positions, coordinate border_offset)
Write a gamemap in to a vector string.
const terrain_code FOGGED
const terrain_code NONE_TERRAIN
Definition: translation.hpp:58
std::map< std::string, t_string > string_map
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:178
std::string filename
Filename.
Encapsulates the map of the game.
Definition: location.hpp:44
void add(int x_diff, int y_diff)
Definition: location.hpp:174
terrain_code & get(int x, int y)
Definition: translation.hpp:89
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
Helper class, don't construct this directly.
mock_char c
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e