The Battle for Wesnoth  1.19.18+dev
editor_map.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
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_(*this)
45 {
46 }
47 
48 editor_map::editor_map(std::string_view data)
49  : gamemap(data)
50  , selection_(*this)
51 {
52 }
53 
55 {
56  try {
57  return editor_map(data);
58  } catch (const incorrect_map_format_error& e) {
59  throw wrap_exc("format", e.message, "");
60  } catch (const wml_exception& e) {
61  throw wrap_exc("wml", e.user_message, "");
62  } catch (const config::error& e) {
63  throw wrap_exc("config", e.message, "");
64  }
65 }
66 
67 editor_map::editor_map(std::size_t width, std::size_t height, const t_translation::terrain_code & filler)
68  : gamemap(width + 2, height + 2, filler)
69  , selection_(*this)
70 {
71 }
72 
74  : gamemap(map)
75  , selection_(*this)
76 {
77 }
78 
79 std::set<map_location> editor_map::get_contiguous_terrain_tiles(const map_location& start) const
80 {
82  std::set<map_location> result;
83  std::deque<map_location> queue;
84  result.insert(start);
85  queue.push_back(start);
86  //this is basically a breadth-first search along adjacent hexes
87  do {
88  for(const map_location& adj : get_adjacent_tiles(queue.front())) {
89  if (on_board_with_border(adj) && get_terrain(adj) == terrain
90  && result.find(adj) == result.end()) {
91  result.insert(adj);
92  queue.push_back(adj);
93  }
94  }
95  queue.pop_front();
96  } while (!queue.empty());
97  return result;
98 }
99 
101 {
102  std::set<map_location> label_locs;
103  std::string label;
104 
105 
106  for (const auto& pair : special_locations().left) {
107 
108  bool is_number = std::find_if(pair.first.begin(), pair.first.end(), [](char c) { return !std::isdigit(c); }) == pair.first.end();
109  if (is_number) {
110  label = VGETTEXT("Player $side_num", utils::string_map{ { "side_num", pair.first } });
111  }
112  else {
113  label = pair.first;
114  }
115 
116  disp.labels().set_label(pair.second, label);
117  label_locs.insert(pair.second);
118  }
119  return label_locs;
120 }
121 
123 {
125 }
126 
128 {
130 }
131 
132 void editor_map::set_selection(const std::set<map_location>& area)
133 {
134  clear_selection();
135  for(const map_location& loc : area) {
137  }
138 }
139 
140 std::set<map_location> editor_map::selection() const
141 {
143 }
144 
145 std::set<map_location> editor_map::selection_inverse() const
146 {
148 }
149 
151 {
153 }
154 
156 {
158 }
159 
161 {
162  selection_.invert();
163 }
164 
166 {
168 }
169 
171 {
172  return selection_.mask().all();
173 }
174 
176 {
177  return selection_.mask().any();
178 }
179 
181 {
182  return selection_.mask().none();
183 }
184 
185 std::size_t editor_map::num_selected() const
186 {
187  return selection_.mask().count();
188 }
189 
190 void editor_map::resize(int width, int height, int x_offset, int y_offset,
191  const t_translation::terrain_code & filler)
192 {
193  int old_w = w();
194  int old_h = h();
195  if (old_w == width && old_h == height && x_offset == 0 && y_offset == 0) {
196  return;
197  }
198 
199  // Save the old selections before resizing
200  std::set<map_location> old_selection = selection();
201 
202  // Determine the amount of resizing is required
203  const int left_resize = -x_offset;
204  const int right_resize = (width - old_w) + x_offset;
205  const int top_resize = -y_offset;
206  const int bottom_resize = (height - old_h) + y_offset;
207 
208  if(right_resize > 0) {
209  expand_right(right_resize, filler);
210  } else if(right_resize < 0) {
211  shrink_right(-right_resize);
212  }
213  if(bottom_resize > 0) {
214  expand_bottom(bottom_resize, filler);
215  } else if(bottom_resize < 0) {
216  shrink_bottom(-bottom_resize);
217  }
218  if(left_resize > 0) {
219  expand_left(left_resize, filler);
220  } else if(left_resize < 0) {
221  shrink_left(-left_resize);
222  }
223  if(top_resize > 0) {
224  expand_top(top_resize, filler);
225  } else if(top_resize < 0) {
226  shrink_top(-top_resize);
227  }
228 
229  // Reset and resize selection mask
230  selection_ = selection_mask{*this};
231 
232  for(const map_location& loc : old_selection) {
233  add_to_selection(loc.plus(-x_offset, -y_offset));
234  }
235 
236  // fix the starting positions
237  if(x_offset || y_offset) {
238  for (auto it = special_locations().left.begin(); it != special_locations().left.end(); ++it) {
239  special_locations().left.modify_data(it, [=](t_translation::coordinate & loc) { loc.add(-x_offset, -y_offset); });
240  }
241  }
242 
243  villages_.clear();
244 
245  //
246  // NOTE: I'm not sure how inefficient it is to check every loc for its village-ness as
247  // opposed to operating on the villages_ vector itself and figuring out how to handle
248  // villages on the map border. Essentially, it's possible to simply remove all members
249  // from villages_ that are no longer on the map after a resize (including those that
250  // land on a border), but that doesn't account for villages that were *on* the border
251  // prior to resizing. Those should be included. As a catch-all fix, I just check every
252  // hex. It's possible that any more complex shenanigans would be even more inefficient.
253  //
254  // -- vultraz, 2018-02-25
255  //
256  for_each_loc([this](const map_location& loc) {
257  if(is_village(loc)) {
258  villages_.push_back(loc);
259  }
260  });
261 }
262 
263 gamemap editor_map::mask_to(const gamemap& target) const
264 {
265  if (target.w() != w() || target.h() != h()) {
266  throw editor_action_exception(_("The size of the target map is different from the current map"));
267  }
268  gamemap mask(target);
269  map_location iter;
270  for (iter.x = -border_size(); iter.x < w() + border_size(); ++iter.x) {
271  for (iter.y = -border_size(); iter.y < h() + border_size(); ++iter.y) {
272  if (target.get_terrain(iter) == get_terrain(iter)) {
274  }
275  }
276  }
277  return mask;
278 }
279 
280 bool editor_map::same_size_as(const gamemap& other) const
281 {
282  return h() == other.h()
283  && w() == other.w();
284 }
285 
287 {
288  t_translation::ter_map tiles_new(tiles().w + count, tiles().h);
289  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
290  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
291  tiles_new.get(x, y) = tiles().get(x, y);
292  }
293  }
294  for (int x = tiles().w, x_end = tiles().w + count; x != x_end; ++x) {
295  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
296  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(tiles().w - 1, y) : filler;
297  }
298  }
299  tiles() = std::move(tiles_new);
300 }
301 
303 {
304  t_translation::ter_map tiles_new(tiles().w + count, tiles().h);
305  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
306  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
307  tiles_new.get(x + count, y) = tiles().get(x, y);
308  }
309  }
310  for (int x = 0, x_end = count; x != x_end; ++x) {
311  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
312  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(0, y) : filler;
313  }
314  }
315  tiles() = std::move(tiles_new);
316 }
317 
319 {
320  t_translation::ter_map tiles_new(tiles().w, tiles().h + count);
321  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
322  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
323  tiles_new.get(x, y + count) = tiles().get(x, y);
324  }
325  }
326  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
327  for (int y = 0, y_end = count; y != y_end; ++y) {
328  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(x, 0) : filler;
329  }
330  }
331  tiles() = std::move(tiles_new);
332 }
333 
335 {
336  t_translation::ter_map tiles_new(tiles().w, tiles().h + count);
337  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
338  for (int y = 0, y_end = tiles().h; y != y_end; ++y) {
339  tiles_new.get(x, y) = tiles().get(x, y);
340  }
341  }
342  for (int x = 0, x_end = tiles().w; x != x_end; ++x) {
343  for (int y = tiles().h, y_end = tiles().h + count; y != y_end; ++y) {
344  tiles_new.get(x, y) = filler == t_translation::NONE_TERRAIN ? tiles().get(x, tiles().h - 1) : filler;
345  }
346  }
347  tiles() = std::move(tiles_new);
348 }
349 
351 {
352  if(count < 0 || count > tiles().w) {
354  }
355  t_translation::ter_map tiles_new(tiles().w - count, tiles().h);
356  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
357  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
358  tiles_new.get(x, y) = tiles().get(x, y);
359  }
360  }
361  tiles() = std::move(tiles_new);
362 }
363 
364 void editor_map::shrink_left(int count)
365 {
366  if (count < 0 || count > tiles().w) {
368  }
369  t_translation::ter_map tiles_new(tiles().w - count, tiles().h);
370  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
371  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
372  tiles_new.get(x, y) = tiles().get(x + count, y);
373  }
374  }
375  tiles() = std::move(tiles_new);
376 }
377 
378 void editor_map::shrink_top(int count)
379 {
380  if (count < 0 || count > tiles().h) {
382  }
383  t_translation::ter_map tiles_new(tiles().w, tiles().h - count);
384  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
385  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
386  tiles_new.get(x, y) = tiles().get(x, y + count);
387  }
388  }
389  tiles() = std::move(tiles_new);
390 }
391 
393 {
394  if (count < 0 || count > tiles().h) {
396  }
397  t_translation::ter_map tiles_new(tiles().w, tiles().h - count);
398  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
399  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
400  tiles_new.get(x, y) = tiles().get(x, y);
401  }
402  }
403  tiles() = std::move(tiles_new);
404 }
405 
406 
407 
408 } //end namespace editor
Base class for editor actions.
map_location loc
Definition: move.cpp:172
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
map_labels & labels()
Definition: display.cpp:2426
static std::set< map_location > get_locations(const selection_mask &mask)
Definition: editor_map.hpp:269
bool select(const map_location &loc)
Marks loc as selected.
Definition: editor_map.hpp:228
const boost::dynamic_bitset< uint64_t > & mask() const
Read-only access to the underlying bitset.
Definition: editor_map.hpp:283
selection_mask inverted() const
Definition: editor_map.hpp:262
bool selected(const map_location &loc) const
Definition: editor_map.hpp:242
bool deselect(const map_location &loc)
Marks loc as unselected.
Definition: editor_map.hpp:237
This class adds extra editor-specific functionality to a normal gamemap.
Definition: editor_map.hpp:70
void shrink_left(int count)
Definition: editor_map.cpp:364
bool everything_selected() const
Definition: editor_map.cpp:170
bool add_to_selection(const map_location &loc)
Add a location to the selection.
Definition: editor_map.cpp:127
bool anything_selected() const
Definition: editor_map.cpp:175
void shrink_top(int count)
Definition: editor_map.cpp:378
void expand_left(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:302
selection_mask selection_
The selected hexes.
Definition: editor_map.hpp:311
std::set< map_location > set_starting_position_labels(display &disp)
Set labels for staring positions in the given display object.
Definition: editor_map.cpp:100
void shrink_bottom(int count)
Definition: editor_map.cpp:392
static editor_map from_string(std::string_view data)
Wrapper around editor_map(cfg, data) that catches possible exceptions and wraps them in a editor_map_...
Definition: editor_map.cpp:54
void invert_selection()
Invert the selection, i.e.
Definition: editor_map.cpp:160
void shrink_right(int count)
Definition: editor_map.cpp:350
void select_all()
Select all map hexes.
Definition: editor_map.cpp:165
bool same_size_as(const gamemap &other) const
A precondition to several map operations.
Definition: editor_map.cpp:280
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:263
std::set< map_location > selection() const
Return the selection set.
Definition: editor_map.cpp:140
bool nothing_selected() const
Definition: editor_map.cpp:180
void expand_right(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:286
bool in_selection(const map_location &loc) const
Definition: editor_map.cpp:122
bool remove_from_selection(const map_location &loc)
Remove a location to the selection.
Definition: editor_map.cpp:150
void expand_top(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:318
void set_selection(const std::set< map_location > &area)
Select the given area.
Definition: editor_map.cpp:132
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:79
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:190
std::set< map_location > selection_inverse() const
Returns a set of all hexes not currently selected.
Definition: editor_map.cpp:145
editor_map()
Empty map constructor.
Definition: editor_map.cpp:42
void clear_selection()
Clear the selection.
Definition: editor_map.cpp:155
void expand_bottom(int count, const t_translation::terrain_code &filler)
Definition: editor_map.cpp:334
std::size_t num_selected() const
Definition: editor_map.cpp:185
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:271
int w() const
Effective map width.
Definition: map.hpp:50
terrain_map & tiles()
Definition: map.hpp:161
int h() const
Effective map height.
Definition: map.hpp:53
void for_each_loc(const F &f) const
Definition: map.hpp:140
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:358
int border_size() const
Size of the map border.
Definition: map.hpp:56
location_map & special_locations()
Definition: map.hpp:105
Encapsulates the map of the game.
Definition: map.hpp:176
bool is_village(const map_location &loc) const
Definition: map.cpp:66
std::vector< map_location > villages_
Definition: map.hpp:258
gamemap_base::set_terrain_result 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:365
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:148
map_display and display: classes which take care of displaying the map and game-data on the screen.
#define WRN_ED
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:201
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
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.
constexpr terrain_code NONE_TERRAIN
Definition: translation.hpp:58
const terrain_code FOGGED
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
Definition: general.hpp:151
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:188
std::string filename
Filename.
Encapsulates the map of the game.
Definition: location.hpp:46
void add(int x_diff, int y_diff)
Definition: location.hpp:192
map_location plus(int x_diff, int y_diff) const
Definition: location.hpp:193
terrain_code & get(int x, int y)
Definition: translation.hpp:83
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