The Battle for Wesnoth  1.19.17+dev
map.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
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 /**
17  * @file
18  * Routines related to game-maps, terrain, locations, directions. etc.
19  */
20 
21 #include "map/map.hpp"
22 
23 #include "config.hpp"
24 #include "log.hpp"
25 #include "map/exception.hpp"
27 #include "terrain/terrain.hpp"
28 #include "terrain/type_data.hpp"
29 #include "utils/general.hpp"
30 #include "wml_exception.hpp"
31 
32 #include <algorithm>
33 #include <sstream>
34 #include <utility>
35 
36 static lg::log_domain log_config("config");
37 #define ERR_CF LOG_STREAM(err, log_config)
38 #define LOG_G LOG_STREAM(info, lg::general())
39 #define DBG_G LOG_STREAM(debug, lg::general())
40 
41 /** Gets the list of terrains. */
43 {
44  return tdata_->list();
45 }
46 
47 /** Shortcut to get_terrain_info(get_terrain(loc)). */
49 {
50  return tdata_->get_terrain_info(get_terrain(loc));
51 }
52 
59 std::string gamemap::get_terrain_string(const map_location& loc) const
60  { return get_terrain_string(get_terrain(loc)); }
63 
65  { return on_board(loc) && is_village(get_terrain(loc)); }
67  { return on_board(loc) ? gives_healing(get_terrain(loc)) : 0; }
69  { return on_board(loc) && is_castle(get_terrain(loc)); }
70 bool gamemap::is_keep(const map_location& loc) const
71  { return on_board(loc) && is_keep(get_terrain(loc)); }
72 
73 
74 /* Forwarded methods of tdata_ */
76  { return tdata_->underlying_mvt_terrain(terrain); }
78  { return tdata_->underlying_def_terrain(terrain); }
80  { return tdata_->underlying_union_terrain(terrain); }
81 std::string gamemap::get_terrain_string(const t_translation::terrain_code & terrain) const
82  { return tdata_->get_terrain_string(terrain); }
84  { return tdata_->get_terrain_editor_string(terrain); }
86  { return tdata_->get_underlying_terrain_string(terrain); }
88  { return tdata_->get_terrain_info(terrain).is_village(); }
90  { return tdata_->get_terrain_info(terrain).gives_healing(); }
92  { return tdata_->get_terrain_info(terrain).is_castle(); }
93 bool gamemap::is_keep(const t_translation::terrain_code & terrain) const
94  { return tdata_->get_terrain_info(terrain).is_keep(); }
95 
97  { return tdata_->get_terrain_info(terrain); }
98 
100 {
102 }
103 
104 gamemap::gamemap(const std::string& data)
105  : gamemap_base(1, 1)
106  , tdata_(terrain_type_data::get())
107  , villages_()
108 {
109  DBG_G << "loading map: '" << data << "'";
110  read(data);
111 }
112 
114  : tiles_(w, h, t)
115  , starting_positions_()
116 {
117 
118 }
119 
121 {
122 }
123 
124 void gamemap::read(const std::string& data, const bool allow_invalid)
125 {
127  villages_.clear();
128  special_locations().clear();
129 
130  if(data.empty()) {
131  if(allow_invalid) return;
132  }
133 
134  try {
135  const auto border_offset = t_translation::coordinate{border_size(), border_size()};
137 
138  } catch(const t_translation::error& e) {
139  // We re-throw the error but as map error.
140  // Since all codepaths test for this, it's the least work.
141  throw incorrect_map_format_error(e.message);
142  }
143 
144  // Post processing on the map
145  VALIDATE((total_width() >= 1 && total_height() >= 1), "A map needs at least 1 tile, the map cannot be loaded.");
146 
147  for(int x = 0; x < total_width(); ++x) {
148  for(int y = 0; y < total_height(); ++y) {
149 
150  // Is the terrain valid?
152  if(tdata_->map().count(t) == 0) {
153  if(!tdata_->is_known(t)) {
154  std::stringstream ss;
155  ss << "Unknown tile in map: (" << t_translation::write_terrain_code(t)
156  << ") '" << t << "'";
157  throw incorrect_map_format_error(ss.str().c_str());
158  }
159  }
160 
161  // Is it a village?
162  if(x >= border_size() && y >= border_size()
163  && x < total_width()- border_size() && y < total_height()- border_size()
164  && tdata_->is_village(tiles().get(x, y))) {
165  villages_.push_back(map_location(x - border_size(), y - border_size()));
166  }
167  }
168  }
169 }
170 
171 std::string_view gamemap::strip_legacy_header(std::string_view data) const
172 {
173  // Test whether there is a header section
174  std::size_t header_offset = data.find("\n\n");
175  if(header_offset == std::string::npos) {
176  // For some reason Windows will fail to load a file with \r\n
177  // lineending properly no problems on Linux with those files.
178  // This workaround fixes the problem the copy later will copy
179  // the second \r\n to the map, but that's no problem.
180  header_offset = data.find("\r\n\r\n");
181  }
182  const std::size_t comma_offset = data.find(",");
183  // The header shouldn't contain commas, so if the comma is found
184  // before the header, we hit a \n\n inside or after a map.
185  // This is no header, so don't parse it as it would be.
186  if(header_offset == std::string::npos || comma_offset < header_offset) {
187  return data;
188  }
189 
190  return data.substr(header_offset + 2);
191 }
192 
193 std::string gamemap::write() const
194 {
196 }
197 
198 void gamemap_base::overlay(const gamemap_base& m, map_location loc, const std::vector<overlay_rule>& rules, bool m_is_odd, bool ignore_special_locations)
199 {
200  int xpos = loc.wml_x();
201  int ypos = loc.wml_y();
202 
203  const int xstart = std::max<int>(0, -xpos);
204  const int xend = std::min<int>(m.total_width(), total_width() - xpos);
205  const int xoffset = xpos;
206 
207  const int ystart_even = std::max<int>(0, -ypos);
208  const int yend_even = std::min<int>(m.total_height(), total_height() - ypos);
209  const int yoffset_even = ypos;
210 
211  const int ystart_odd = std::max<int>(0, -ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
212  const int yend_odd = std::min<int>(m.total_height(), total_height() - ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
213  const int yoffset_odd = ypos -(xpos & 1) + (m_is_odd ? 1 : 0);
214 
215  for(int x1 = xstart; x1 != xend; ++x1) {
216  int ystart, yend, yoffset;
217  if(x1 & 1) {
218  ystart = ystart_odd;
219  yend = yend_odd;
220  yoffset = yoffset_odd;
221  }
222  else {
223  ystart = ystart_even;
224  yend = yend_even;
225  yoffset = yoffset_even;
226  }
227  for(int y1 = ystart; y1 != yend; ++y1) {
228  const int x2 = x1 + xoffset;
229  const int y2 = y1 + yoffset;
230 
231  const t_translation::terrain_code t = m.get_terrain({x1,y1, wml_loc()});
232  const t_translation::terrain_code current = get_terrain({x2, y2, wml_loc()});
233 
235  continue;
236  }
237 
238  // See if there is a matching rule
239  const overlay_rule* rule = nullptr;
240  for(const overlay_rule& current_rule : rules)
241  {
242  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
243  continue;
244  }
245  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
246  continue;
247  }
248  rule = &current_rule;
249  break;
250  }
251 
252  if (!rule) {
254  }
255  else if(!rule->use_old_) {
256  set_terrain(map_location(x2, y2, wml_loc()), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
257  }
258  }
259  }
260 
261  if (!ignore_special_locations) {
262  for(auto& pair : m.special_locations().left) {
263 
264  int x = pair.second.wml_x();
265  int y = pair.second.wml_y();
266  if(x & 1) {
267  if(x < xstart || x >= xend || y < ystart_odd || y >= yend_odd) {
268  continue;
269  }
270  }
271  else {
272  if(x < xstart || x >= xend || y < ystart_even || y >= yend_even) {
273  continue;
274  }
275  }
276  int x_new = x + xoffset;
277  int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even);
278  map_location pos_new = map_location(x_new, y_new, wml_loc());
279 
280  starting_positions_.left.erase(pair.first);
281  starting_positions_.insert(location_map::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y)));
282  }
283  }
284 }
286 {
287 
289  return tiles_.get(loc.x + border_size(), loc.y + border_size());
290  }
291 
293 }
294 
295 map_location gamemap_base::special_location(const std::string& id) const
296 {
297  auto it = starting_positions_.left.find(id);
298  if (it != starting_positions_.left.end()) {
299  auto& coordinate = it->second;
301  }
302  else {
303  return map_location();
304  }
305 }
306 
308 {
309  return special_location(std::to_string(n));
310 }
311 
312 namespace {
313  bool is_number(const std::string& id) {
314  return std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
315  }
316 }
317 
319 {
320  int res = 0;
321  for (auto pair : starting_positions_) {
322  const std::string& id = pair.left;
323  if (is_number(id)) {
324  res = std::max(res, std::stoi(id));
325  }
326  }
327  return res;
328 }
329 
331 {
332  if(const std::string* locName = is_special_location(loc)) {
333  if(is_number(*locName)) {
334  return std::stoi(*locName);
335  }
336  }
337  return 0;
338 }
339 
340 const std::string* gamemap_base::is_special_location(const map_location& loc) const
341 {
342  auto it = starting_positions_.right.find(loc);
343  return it == starting_positions_.right.end() ? nullptr : &it->second;
344 }
345 
346 void gamemap_base::set_special_location(const std::string& id, const map_location& loc)
347 {
348  bool valid = loc.valid();
349  auto it_left = starting_positions_.left.find(id);
350  if (it_left != starting_positions_.left.end()) {
351  if (valid) {
352  starting_positions_.left.replace_data(it_left, loc);
353  }
354  else {
355  starting_positions_.left.erase(it_left);
356  }
357  }
358  else {
359  starting_positions_.left.insert(it_left, std::pair(id, loc));
360  }
361 }
362 
364 {
365  set_special_location(std::to_string(side), loc);
366 }
367 
369 {
370  return loc.valid() && loc.x < w() && loc.y < h();
371 }
372 
374 {
375  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
376  loc.x >= -border_size() && loc.x < w() + border_size() &&
377  loc.y >= -border_size() && loc.y < h() + border_size();
378 }
379 
381  const t_translation::terrain_code& terrain,
383  bool replace_if_failed)
384 {
386  auto& [new_terrain, village_state] = res;
387 
388  // off the map: ignore request
389  if(!on_board_with_border(loc)) {
390  DBG_G << "set_terrain: " << loc << " is not on the map.";
391  return res;
392  }
393 
394  new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
395 
396  if(new_terrain == t_translation::NONE_TERRAIN) {
397  return res;
398  }
399 
400  if(on_board(loc)) {
401  const bool old_village = is_village(loc);
402  const bool new_village = tdata_->is_village(new_terrain);
403 
404  if(old_village && !new_village) {
407  } else if(!old_village && new_village) {
408  villages_.push_back(loc);
410  }
411  }
412 
413  (*this)[loc] = new_terrain;
414  return res;
415 }
416 
417 std::vector<map_location> gamemap_base::parse_location_range(const std::string &x, const std::string &y,
418  bool with_border) const
419 {
420  std::vector<map_location> res;
421  const std::vector<std::string> xvals = utils::split(x);
422  const std::vector<std::string> yvals = utils::split(y);
423  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
424  if (with_border) {
425  int bs = border_size();
426  xmin -= bs;
427  xmax += bs;
428  ymin -= bs;
429  ymax += bs;
430  }
431 
432  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
433  {
434  std::pair<int,int> xrange, yrange;
435 
436  if (i < xvals.size()) {
437  xrange = utils::parse_range(xvals[i]);
438  if (xrange.first < xmin) xrange.first = xmin;
439  if (xrange.second > xmax) xrange.second = xmax;
440  } else {
441  xrange.first = xmin;
442  xrange.second = xmax;
443  }
444 
445  if (i < yvals.size()) {
446  yrange = utils::parse_range(yvals[i]);
447  if (yrange.first < ymin) yrange.first = ymin;
448  if (yrange.second > ymax) yrange.second = ymax;
449  } else {
450  yrange.first = ymin;
451  yrange.second = ymax;
452  }
453 
454  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
455  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
456  res.emplace_back(x2-1,y2-1);
457  }
458  }
459  }
460  return res;
461 }
462 
463 std::string gamemap_base::to_string() const
464 {
465  return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n";
466 }
467 
468 const std::vector<map_location> gamemap_base::starting_positions() const {
470  std::vector<map_location> res;
471  for(int i = 1; i <= n; i++) {
472  res.push_back(starting_position(i));
473  }
474  return res;
475 }
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
virtual ~gamemap_base()
Definition: map.cpp:120
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:285
int w() const
Effective map width.
Definition: map.hpp:50
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:363
terrain_map & tiles()
Definition: map.hpp:158
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:346
int h() const
Effective map height.
Definition: map.hpp:53
void overlay(const gamemap_base &m, map_location loc, const std::vector< overlay_rule > &rules=std::vector< overlay_rule >(), bool is_odd=false, bool ignore_special_locations=false)
Overlays another map onto this one at the given position.
Definition: map.cpp:198
map_location special_location(const std::string &id) const
Definition: map.cpp:295
gamemap_base()=default
std::vector< map_location > parse_location_range(const std::string &xvals, const std::string &yvals, bool with_border=false) const
Parses ranges of locations into a vector of locations, using this map's dimensions as bounds.
Definition: map.cpp:417
map_location starting_position(int side) const
Definition: map.cpp:307
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:59
std::string to_string() const
Definition: map.cpp:463
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:318
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:373
int total_height() const
Real height of the map, including borders.
Definition: map.hpp:62
int is_starting_position(const map_location &loc) const
returns the side number of the side starting at position loc, 0 if no such side exists.
Definition: map.cpp:330
virtual 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)=0
Clobbers over the terrain at location 'loc', with the given terrain.
location_map starting_positions_
Definition: map.hpp:162
village_state
What happens to a village hex when its terrain is changed.
Definition: map.hpp:77
const std::string * is_special_location(const map_location &loc) const
returns the name of the special location at position loc, null if no such location exists.
Definition: map.cpp:340
int border_size() const
Size of the map border.
Definition: map.hpp:56
const std::vector< map_location > starting_positions() const
Definition: map.cpp:468
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:368
location_map & special_locations()
Definition: map.hpp:102
terrain_map tiles_
Definition: map.hpp:161
const t_translation::ter_list & underlying_union_terrain(const map_location &loc) const
Definition: map.cpp:57
bool is_village(const map_location &loc) const
Definition: map.cpp:64
std::string get_underlying_terrain_string(const t_translation::terrain_code &terrain) const
Definition: map.cpp:85
bool is_castle(const map_location &loc) const
Definition: map.cpp:68
void read(const std::string &data, const bool allow_invalid=true)
Definition: map.cpp:124
const t_translation::ter_list & underlying_mvt_terrain(const map_location &loc) const
Definition: map.cpp:53
const t_translation::ter_list & underlying_def_terrain(const map_location &loc) const
Definition: map.cpp:55
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:42
std::string write() const
Definition: map.cpp:193
std::vector< map_location > villages_
Definition: map.hpp:261
std::string get_terrain_editor_string(const map_location &loc) const
Definition: map.cpp:61
void write_terrain(const map_location &loc, config &cfg) const
Writes the terrain at loc to cfg.
Definition: map.cpp:99
std::string_view strip_legacy_header(std::string_view data) const
Returns a subview of data which excludes any legacy headers.
Definition: map.cpp:171
gamemap(const std::string &data)
Loads a map.
Definition: map.cpp:104
bool is_keep(const map_location &loc) const
Definition: map.cpp:70
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:59
std::shared_ptr< terrain_type_data > tdata_
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:380
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:96
int gives_healing(const map_location &loc) const
Definition: map.cpp:66
Contains the database of all known terrain types, both those defined explicitly by WML [terrain_type]...
Definition: type_data.hpp:41
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
std::size_t i
Definition: function.cpp:1032
T end(const std::pair< T, T > &p)
Standard logging facilities (interface).
#define DBG_G
Definition: map.cpp:39
static lg::log_domain log_config("config")
CURSOR_TYPE get()
Definition: cursor.cpp:218
ter_map read_game_map(std::string_view str, starting_positions &starting_positions, coordinate border_offset)
Reads a gamemap string into a 2D vector.
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 VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
std::string write_terrain_code(const terrain_code &tcode)
Writes a single terrain code to a string.
const terrain_code FOGGED
const terrain_code NONE_TERRAIN
Definition: translation.hpp:58
std::size_t erase(Container &container, const Value &value)
Convenience wrapper for using std::remove on a container.
Definition: general.hpp:118
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:156
std::pair< int, int > parse_range(std::string_view str)
Recognises the following patterns, and returns a {min, max} pair.
std::vector< std::string > split(const config_attribute_value &val)
int w
Definition: pathfind.cpp:188
std::string_view data
Definition: picture.cpp:188
terrain_type_data::merge_mode mode_
Definition: map.hpp:127
utils::optional< t_translation::terrain_code > terrain_
Definition: map.hpp:128
Encapsulates the map of the game.
Definition: location.hpp:46
bool valid() const
Definition: location.hpp:111
int wml_y() const
Definition: location.hpp:187
static const map_location & null_location()
Definition: location.hpp:103
int wml_x() const
Definition: location.hpp:186
terrain_code & get(int x, int y)
Definition: translation.hpp:89
std::vector< terrain_code > data
Definition: translation.hpp:92
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
mock_char c
static map_location::direction n
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
#define e
#define h