The Battle for Wesnoth  1.19.18+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 
54  { return tdata_->underlying_mvt_terrain(get_terrain(loc)); }
56  { return tdata_->underlying_def_terrain(get_terrain(loc)); }
58  { return tdata_->underlying_union_terrain(get_terrain(loc)); }
59 std::string gamemap::get_terrain_string(const map_location& loc) const
60  { return get_terrain_string(get_terrain(loc)); }
65 
67  { return on_board(loc) && tdata_->get_terrain_info((*this)[loc]).is_village(); }
69  { return on_board(loc) ? tdata_->get_terrain_info((*this)[loc]).gives_healing() : 0; }
71  { return on_board(loc) && tdata_->get_terrain_info((*this)[loc]).is_castle(); }
72 bool gamemap::is_keep(const map_location& loc) const
73  { return on_board(loc) && tdata_->get_terrain_info((*this)[loc]).is_keep(); }
74 
75 
76 /* Forwarded methods of tdata_ */
77 std::string gamemap::get_terrain_string(const t_translation::terrain_code & terrain) const
78  { return tdata_->get_terrain_string(terrain); }
80  { return tdata_->get_terrain_editor_string(terrain); }
82  { return tdata_->get_underlying_terrain_string(terrain); }
83 
85  { return tdata_->get_terrain_info(terrain); }
86 
88 {
90 }
91 
92 gamemap::gamemap(std::string_view data)
94 {
95  DBG_G << "loading map: '" << data << "'";
96  read(data);
97 }
98 
99 gamemap::gamemap(int w, int h, const terrain_code& t)
100  : gamemap_base(w, h, t)
101  , tdata_(terrain_type_data::get())
102  , villages_()
103 {
104 }
105 
107  : tiles_(w, h, t)
108  , starting_positions_()
109 {
110 }
111 
113 {
114 }
115 
116 void gamemap::read(std::string_view data, const bool allow_invalid)
117 {
119  villages_.clear();
120  special_locations().clear();
121 
122  if(data.empty()) {
123  if(allow_invalid) return;
124  }
125 
126  try {
127  const auto border_offset = t_translation::coordinate{border_size(), border_size()};
129 
130  } catch(const t_translation::error& e) {
131  // We re-throw the error but as map error.
132  // Since all codepaths test for this, it's the least work.
133  throw incorrect_map_format_error(e.message);
134  }
135 
136  // Post processing on the map
137  VALIDATE((total_width() >= 1 && total_height() >= 1), "A map needs at least 1 tile, the map cannot be loaded.");
138 
139  for_each_loc([&](const map_location& loc) {
140  const t_translation::terrain_code& terrain = (*this)[loc];
141 
142  // Is the terrain valid?
143  if(!tdata_->is_known(terrain)) {
144  std::stringstream ss;
145  ss << "Unknown tile in map: (" << t_translation::write_terrain_code(terrain) << ") '" << terrain << "'";
146  throw incorrect_map_format_error(ss.str());
147  }
148 
149  // Note: This excludes village tiles on the border.
150  if(is_village(loc)) {
151  villages_.push_back(loc);
152  }
153  });
154 }
155 
156 std::string_view gamemap::strip_legacy_header(std::string_view data) const
157 {
158  // Test whether there is a header section
159  std::size_t header_offset = data.find("\n\n");
160  if(header_offset == std::string::npos) {
161  // For some reason Windows will fail to load a file with \r\n
162  // lineending properly no problems on Linux with those files.
163  // This workaround fixes the problem the copy later will copy
164  // the second \r\n to the map, but that's no problem.
165  header_offset = data.find("\r\n\r\n");
166  }
167  const std::size_t comma_offset = data.find(",");
168  // The header shouldn't contain commas, so if the comma is found
169  // before the header, we hit a \n\n inside or after a map.
170  // This is no header, so don't parse it as it would be.
171  if(header_offset == std::string::npos || comma_offset < header_offset) {
172  return data;
173  }
174 
175  return data.substr(header_offset + 2);
176 }
177 
178 std::string gamemap::write() const
179 {
181 }
182 
183 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)
184 {
185  int xpos = loc.wml_x();
186  int ypos = loc.wml_y();
187 
188  const int xstart = std::max<int>(0, -xpos);
189  const int xend = std::min<int>(m.total_width(), total_width() - xpos);
190  const int xoffset = xpos;
191 
192  const int ystart_even = std::max<int>(0, -ypos);
193  const int yend_even = std::min<int>(m.total_height(), total_height() - ypos);
194  const int yoffset_even = ypos;
195 
196  const int ystart_odd = std::max<int>(0, -ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
197  const int yend_odd = std::min<int>(m.total_height(), total_height() - ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
198  const int yoffset_odd = ypos -(xpos & 1) + (m_is_odd ? 1 : 0);
199 
200  for(int x1 = xstart; x1 != xend; ++x1) {
201  int ystart, yend, yoffset;
202  if(x1 & 1) {
203  ystart = ystart_odd;
204  yend = yend_odd;
205  yoffset = yoffset_odd;
206  }
207  else {
208  ystart = ystart_even;
209  yend = yend_even;
210  yoffset = yoffset_even;
211  }
212  for(int y1 = ystart; y1 != yend; ++y1) {
213  const int x2 = x1 + xoffset;
214  const int y2 = y1 + yoffset;
215 
216  const t_translation::terrain_code t = m.get_terrain({x1,y1, wml_loc()});
217  const t_translation::terrain_code current = get_terrain({x2, y2, wml_loc()});
218 
220  continue;
221  }
222 
223  // See if there is a matching rule
224  const overlay_rule* rule = nullptr;
225  for(const overlay_rule& current_rule : rules)
226  {
227  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
228  continue;
229  }
230  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
231  continue;
232  }
233  rule = &current_rule;
234  break;
235  }
236 
237  if (!rule) {
239  }
240  else if(!rule->use_old_) {
241  set_terrain(map_location(x2, y2, wml_loc()), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
242  }
243  }
244  }
245 
246  if (!ignore_special_locations) {
247  for(auto& pair : m.special_locations().left) {
248 
249  int x = pair.second.wml_x();
250  int y = pair.second.wml_y();
251  if(x & 1) {
252  if(x < xstart || x >= xend || y < ystart_odd || y >= yend_odd) {
253  continue;
254  }
255  }
256  else {
257  if(x < xstart || x >= xend || y < ystart_even || y >= yend_even) {
258  continue;
259  }
260  }
261  int x_new = x + xoffset;
262  int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even);
263  map_location pos_new = map_location(x_new, y_new, wml_loc());
264 
265  starting_positions_.left.erase(pair.first);
266  starting_positions_.insert(location_map::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y)));
267  }
268  }
269 }
270 
272 {
274  return tiles_.get(loc.x + border_size(), loc.y + border_size());
275  }
276 
278 }
279 
280 map_location gamemap_base::special_location(const std::string& id) const
281 {
282  auto it = starting_positions_.left.find(id);
283  if (it != starting_positions_.left.end()) {
284  auto& coordinate = it->second;
286  }
287  else {
288  return map_location();
289  }
290 }
291 
293 {
294  return special_location(std::to_string(n));
295 }
296 
297 namespace {
298  bool is_number(const std::string& id) {
299  return std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
300  }
301 }
302 
304 {
305  int res = 0;
306  for (auto pair : starting_positions_) {
307  const std::string& id = pair.left;
308  if (is_number(id)) {
309  res = std::max(res, std::stoi(id));
310  }
311  }
312  return res;
313 }
314 
316 {
317  if(const std::string* locName = is_special_location(loc)) {
318  if(is_number(*locName)) {
319  return std::stoi(*locName);
320  }
321  }
322  return 0;
323 }
324 
325 const std::string* gamemap_base::is_special_location(const map_location& loc) const
326 {
327  auto it = starting_positions_.right.find(loc);
328  return it == starting_positions_.right.end() ? nullptr : &it->second;
329 }
330 
331 void gamemap_base::set_special_location(const std::string& id, const map_location& loc)
332 {
333  bool valid = loc.valid();
334  auto it_left = starting_positions_.left.find(id);
335  if (it_left != starting_positions_.left.end()) {
336  if (valid) {
337  starting_positions_.left.replace_data(it_left, loc);
338  }
339  else {
340  starting_positions_.left.erase(it_left);
341  }
342  }
343  else {
344  starting_positions_.left.insert(it_left, std::pair(id, loc));
345  }
346 }
347 
349 {
350  set_special_location(std::to_string(side), loc);
351 }
352 
354 {
355  return loc.valid() && loc.x < w() && loc.y < h();
356 }
357 
359 {
360  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
361  loc.x >= -border_size() && loc.x < w() + border_size() &&
362  loc.y >= -border_size() && loc.y < h() + border_size();
363 }
364 
366  const t_translation::terrain_code& terrain,
368  bool replace_if_failed)
369 {
371  auto& [new_terrain, village_state] = res;
372 
373  // off the map: ignore request
374  if(!on_board_with_border(loc)) {
375  DBG_G << "set_terrain: " << loc << " is not on the map.";
376  return res;
377  }
378 
379  new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
380 
381  if(new_terrain == t_translation::NONE_TERRAIN) {
382  return res;
383  }
384 
385  if(on_board(loc)) {
386  const bool old_village = is_village(loc);
387  const bool new_village = tdata_->is_village(new_terrain);
388 
389  if(old_village && !new_village) {
392  } else if(!old_village && new_village) {
393  villages_.push_back(loc);
395  }
396  }
397 
398  (*this)[loc] = new_terrain;
399  return res;
400 }
401 
402 std::vector<map_location> gamemap_base::parse_location_range(const std::string &x, const std::string &y,
403  bool with_border) const
404 {
405  std::vector<map_location> res;
406  const std::vector<std::string> xvals = utils::split(x);
407  const std::vector<std::string> yvals = utils::split(y);
408  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
409  if (with_border) {
410  int bs = border_size();
411  xmin -= bs;
412  xmax += bs;
413  ymin -= bs;
414  ymax += bs;
415  }
416 
417  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
418  {
419  std::pair<int,int> xrange, yrange;
420 
421  if (i < xvals.size()) {
422  xrange = utils::parse_range(xvals[i]);
423  if (xrange.first < xmin) xrange.first = xmin;
424  if (xrange.second > xmax) xrange.second = xmax;
425  } else {
426  xrange.first = xmin;
427  xrange.second = xmax;
428  }
429 
430  if (i < yvals.size()) {
431  yrange = utils::parse_range(yvals[i]);
432  if (yrange.first < ymin) yrange.first = ymin;
433  if (yrange.second > ymax) yrange.second = ymax;
434  } else {
435  yrange.first = ymin;
436  yrange.second = ymax;
437  }
438 
439  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
440  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
441  res.emplace_back(x2-1,y2-1);
442  }
443  }
444  }
445  return res;
446 }
447 
448 std::string gamemap_base::to_string() const
449 {
450  return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n";
451 }
452 
453 const std::vector<map_location> gamemap_base::starting_positions() const {
455  std::vector<map_location> res;
456  for(int i = 1; i <= n; i++) {
457  res.push_back(starting_position(i));
458  }
459  return res;
460 }
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:112
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
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:348
terrain_map & tiles()
Definition: map.hpp:161
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:331
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:183
map_location special_location(const std::string &id) const
Definition: map.cpp:280
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:402
map_location starting_position(int side) const
Definition: map.cpp:292
void for_each_loc(const F &f) const
Definition: map.hpp:140
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:59
std::string to_string() const
Definition: map.cpp:448
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:303
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:358
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:315
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:165
village_state
What happens to a village hex when its terrain is changed.
Definition: map.hpp:80
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:325
int border_size() const
Size of the map border.
Definition: map.hpp:56
const std::vector< map_location > starting_positions() const
Definition: map.cpp:453
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:353
location_map & special_locations()
Definition: map.hpp:105
terrain_map tiles_
Definition: map.hpp:164
Encapsulates the map of the game.
Definition: map.hpp:176
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:66
bool is_castle(const map_location &loc) const
Definition: map.cpp:70
std::string get_underlying_terrain_string(const map_location &loc) const
Definition: map.cpp:63
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:178
std::vector< map_location > villages_
Definition: map.hpp:258
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:87
std::string_view strip_legacy_header(std::string_view data) const
Returns a subview of data which excludes any legacy headers.
Definition: map.cpp:156
bool is_keep(const map_location &loc) const
Definition: map.cpp:72
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:59
std::shared_ptr< terrain_type_data > tdata_
Definition: map.hpp:255
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_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:84
gamemap(std::string_view data)
Loads a map.
Definition: map.cpp:92
void read(std::string_view data, const bool allow_invalid=true)
Definition: map.cpp:116
int gives_healing(const map_location &loc) const
Definition: map.cpp:68
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.
constexpr terrain_code NONE_TERRAIN
Definition: translation.hpp:58
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
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
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::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:130
utils::optional< t_translation::terrain_code > terrain_
Definition: map.hpp:131
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
int wml_x() const
Definition: location.hpp:186
terrain_code & get(int x, int y)
Definition: translation.hpp:83
std::vector< terrain_code > data
Definition: translation.hpp:86
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