The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
23#include "gettext.hpp"
24#include "map/exception.hpp"
25#include "map/label.hpp"
26#include "wml_exception.hpp"
27
28namespace editor {
29
30editor_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
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
67editor_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
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
126
131
132void editor_map::set_selection(const std::set<map_location>& area)
133{
135 for(const map_location& loc : area) {
137 }
138}
139
140std::set<map_location> editor_map::selection() const
141{
143}
144
145std::set<map_location> editor_map::selection_inverse() const
146{
148}
149
154
159
164
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
185std::size_t editor_map::num_selected() const
186{
187 return selection_.mask().count();
188}
189
190void 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
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
280bool 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
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
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:2392
bool select(const map_location &loc)
Marks loc as selected.
static std::set< map_location > get_locations(const selection_mask &mask)
selection_mask inverted() const
bool selected(const map_location &loc) const
bool deselect(const map_location &loc)
Marks loc as unselected.
const boost::dynamic_bitset< uint64_t > & mask() const
Read-only access to the underlying bitset.
This class adds extra editor-specific functionality to a normal gamemap.
void shrink_left(int count)
bool everything_selected() const
bool add_to_selection(const map_location &loc)
Add a location to the selection.
bool anything_selected() const
void shrink_top(int count)
void expand_left(int count, const t_translation::terrain_code &filler)
selection_mask selection_
The selected hexes.
std::set< map_location > set_starting_position_labels(display &disp)
Set labels for staring positions in the given display object.
void shrink_bottom(int count)
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_...
void invert_selection()
Invert the selection, i.e.
void shrink_right(int count)
void select_all()
Select all map hexes.
bool same_size_as(const gamemap &other) const
A precondition to several map operations.
gamemap mask_to(const gamemap &target) const
A sort-of diff operation returning a mask that, when applied to the current editor_map,...
std::set< map_location > selection() const
Return the selection set.
bool nothing_selected() const
void expand_right(int count, const t_translation::terrain_code &filler)
bool in_selection(const map_location &loc) const
bool remove_from_selection(const map_location &loc)
Remove a location to the selection.
void expand_top(int count, const t_translation::terrain_code &filler)
void set_selection(const std::set< map_location > &area)
Select the given area.
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.
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.
std::set< map_location > selection_inverse() const
Returns a set of all hexes not currently selected.
editor_map()
Empty map constructor.
void clear_selection()
Clear the selection.
void expand_bottom(int count, const t_translation::terrain_code &filler)
std::size_t num_selected() const
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition map.cpp:265
int w() const
Effective map width.
Definition map.hpp:50
location_map & special_locations()
Definition map.hpp:105
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:352
terrain_map & tiles()
Definition map.hpp:161
int border_size() const
Size of the map border.
Definition map.hpp:56
Encapsulates the map of the game.
Definition map.hpp:176
bool is_village(const map_location &loc) const
Definition map.cpp:60
std::vector< map_location > villages_
Definition map.hpp:252
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:359
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:100
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.
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
const terrain_code FOGGED
std::map< std::string, t_string > string_map
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)
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Helper class, don't construct this directly.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e
#define c