The Battle for Wesnoth  1.19.0-dev
map.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 "game_config_manager.hpp"
25 #include "log.hpp"
26 #include "map/exception.hpp"
27 #include "serialization/parser.hpp"
29 #include "terrain/terrain.hpp"
30 #include "terrain/type_data.hpp"
31 #include "wml_exception.hpp"
32 
33 #include <algorithm>
34 #include <sstream>
35 #include <utility>
36 
37 static lg::log_domain log_config("config");
38 #define ERR_CF LOG_STREAM(err, log_config)
39 #define LOG_G LOG_STREAM(info, lg::general())
40 #define DBG_G LOG_STREAM(debug, lg::general())
41 
42 /** Gets the list of terrains. */
44 {
45  return tdata_->list();
46 }
47 
48 /** Shortcut to get_terrain_info(get_terrain(loc)). */
50 {
51  return tdata_->get_terrain_info(get_terrain(loc));
52 }
53 
55  { return underlying_mvt_terrain(get_terrain(loc)); }
57  { return underlying_def_terrain(get_terrain(loc)); }
59  { return underlying_union_terrain(get_terrain(loc)); }
60 std::string gamemap::get_terrain_string(const map_location& loc) const
61  { return get_terrain_string(get_terrain(loc)); }
62 std::string gamemap::get_terrain_editor_string(const map_location& loc) const
63  { return get_terrain_editor_string(get_terrain(loc)); }
64 
65 bool gamemap::is_village(const map_location& loc) const
66  { return on_board(loc) && is_village(get_terrain(loc)); }
67 int gamemap::gives_healing(const map_location& loc) const
68  { return on_board(loc) ? gives_healing(get_terrain(loc)) : 0; }
69 bool gamemap::is_castle(const map_location& loc) const
70  { return on_board(loc) && is_castle(get_terrain(loc)); }
71 bool gamemap::is_keep(const map_location& loc) const
72  { return on_board(loc) && is_keep(get_terrain(loc)); }
73 
74 
75 /* Forwarded methods of tdata_ */
77  { return tdata_->underlying_mvt_terrain(terrain); }
79  { return tdata_->underlying_def_terrain(terrain); }
81  { return tdata_->underlying_union_terrain(terrain); }
82 std::string gamemap::get_terrain_string(const t_translation::terrain_code & terrain) const
83  { return tdata_->get_terrain_string(terrain); }
85  { return tdata_->get_terrain_editor_string(terrain); }
87  { return tdata_->get_underlying_terrain_string(terrain); }
89  { return tdata_->get_terrain_info(terrain).is_village(); }
91  { return tdata_->get_terrain_info(terrain).gives_healing(); }
93  { return tdata_->get_terrain_info(terrain).is_castle(); }
94 bool gamemap::is_keep(const t_translation::terrain_code & terrain) const
95  { return tdata_->get_terrain_info(terrain).is_keep(); }
96 
98  { return tdata_->get_terrain_info(terrain); }
99 
100 void gamemap::write_terrain(const map_location &loc, config& cfg) const
101 {
102  cfg["terrain"] = t_translation::write_terrain_code(get_terrain(loc));
103 }
104 
105 gamemap::gamemap(const std::string& data):
106  gamemap_base(1, 1),
107  tdata_(),
108  villages_()
109 {
110  if(const auto* gcm = game_config_manager::get()) {
111  tdata_ = gcm->terrain_types();
112  } else {
113  // Should only be encountered in unit tests
114  tdata_ = std::make_shared<terrain_type_data>(game_config_view::wrap({}));
115  }
116 
117  DBG_G << "loading map: '" << data << "'";
118  read(data);
119 }
120 
122  : tiles_(w, h, t)
123  , starting_positions_()
124 {
125 
126 }
127 
129 {
130 }
131 
132 void gamemap::read(const std::string& data, const bool allow_invalid)
133 {
135  villages_.clear();
136  special_locations().clear();
137 
138  if(data.empty()) {
139  if(allow_invalid) return;
140  }
141 
142  int offset = read_header(data);
143 
144  const std::string& data_only = std::string(data, offset);
145 
146  try {
148 
149  } catch(const t_translation::error& e) {
150  // We re-throw the error but as map error.
151  // Since all codepaths test for this, it's the least work.
152  throw incorrect_map_format_error(e.message);
153  }
154 
155  // Post processing on the map
156  VALIDATE((total_width() >= 1 && total_height() >= 1), "A map needs at least 1 tile, the map cannot be loaded.");
157 
158  for(int x = 0; x < total_width(); ++x) {
159  for(int y = 0; y < total_height(); ++y) {
160 
161  // Is the terrain valid?
163  if(tdata_->map().count(t) == 0) {
164  if(!tdata_->is_known(t)) {
165  std::stringstream ss;
166  ss << "Unknown tile in map: (" << t_translation::write_terrain_code(t)
167  << ") '" << t << "'";
168  throw incorrect_map_format_error(ss.str().c_str());
169  }
170  }
171 
172  // Is it a village?
173  if(x >= border_size() && y >= border_size()
174  && x < total_width()- border_size() && y < total_height()- border_size()
175  && tdata_->is_village(tiles().get(x, y))) {
176  villages_.push_back(map_location(x - border_size(), y - border_size()));
177  }
178  }
179  }
180 }
181 
182 int gamemap::read_header(const std::string& data)
183 {
184  // Test whether there is a header section
185  std::size_t header_offset = data.find("\n\n");
186  if(header_offset == std::string::npos) {
187  // For some reason Windows will fail to load a file with \r\n
188  // lineending properly no problems on Linux with those files.
189  // This workaround fixes the problem the copy later will copy
190  // the second \r\n to the map, but that's no problem.
191  header_offset = data.find("\r\n\r\n");
192  }
193  const std::size_t comma_offset = data.find(",");
194  // The header shouldn't contain commas, so if the comma is found
195  // before the header, we hit a \n\n inside or after a map.
196  // This is no header, so don't parse it as it would be.
197 
198  if (!(!(header_offset == std::string::npos || comma_offset < header_offset)))
199  return 0;
200 
201  std::string header_str(std::string(data, 0, header_offset + 1));
202  config header;
203  ::read(header, header_str);
204 
205  return header_offset + 2;
206 }
207 
208 
209 std::string gamemap::write() const
210 {
212 }
213 
214 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)
215 {
216  int xpos = loc.wml_x();
217  int ypos = loc.wml_y();
218 
219  const int xstart = std::max<int>(0, -xpos);
220  const int xend = std::min<int>(m.total_width(), total_width() - xpos);
221  const int xoffset = xpos;
222 
223  const int ystart_even = std::max<int>(0, -ypos);
224  const int yend_even = std::min<int>(m.total_height(), total_height() - ypos);
225  const int yoffset_even = ypos;
226 
227  const int ystart_odd = std::max<int>(0, -ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
228  const int yend_odd = std::min<int>(m.total_height(), total_height() - ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
229  const int yoffset_odd = ypos -(xpos & 1) + (m_is_odd ? 1 : 0);
230 
231  for(int x1 = xstart; x1 != xend; ++x1) {
232  int ystart, yend, yoffset;
233  if(x1 & 1) {
234  ystart = ystart_odd;
235  yend = yend_odd;
236  yoffset = yoffset_odd;
237  }
238  else {
239  ystart = ystart_even;
240  yend = yend_even;
241  yoffset = yoffset_even;
242  }
243  for(int y1 = ystart; y1 != yend; ++y1) {
244  const int x2 = x1 + xoffset;
245  const int y2 = y1 + yoffset;
246 
247  const t_translation::terrain_code t = m.get_terrain({x1,y1, wml_loc()});
248  const t_translation::terrain_code current = get_terrain({x2, y2, wml_loc()});
249 
251  continue;
252  }
253 
254  // See if there is a matching rule
255  const overlay_rule* rule = nullptr;
256  for(const overlay_rule& current_rule : rules)
257  {
258  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
259  continue;
260  }
261  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
262  continue;
263  }
264  rule = &current_rule;
265  break;
266  }
267 
268  if (!rule) {
270  }
271  else if(!rule->use_old_) {
272  set_terrain(map_location(x2, y2, wml_loc()), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
273  }
274  }
275  }
276 
277  if (!ignore_special_locations) {
278  for(auto& pair : m.special_locations().left) {
279 
280  int x = pair.second.wml_x();
281  int y = pair.second.wml_y();
282  if(x & 1) {
283  if(x < xstart || x >= xend || y < ystart_odd || y >= yend_odd) {
284  continue;
285  }
286  }
287  else {
288  if(x < xstart || x >= xend || y < ystart_even || y >= yend_even) {
289  continue;
290  }
291  }
292  int x_new = x + xoffset;
293  int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even);
294  map_location pos_new = map_location(x_new, y_new, wml_loc());
295 
296  starting_positions_.left.erase(pair.first);
297  starting_positions_.insert(location_map::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y)));
298  }
299  }
300 }
302 {
303 
304  if(on_board_with_border(loc)) {
305  return tiles_.get(loc.x + border_size(), loc.y + border_size());
306  }
307 
309 }
310 
311 map_location gamemap_base::special_location(const std::string& id) const
312 {
313  auto it = starting_positions_.left.find(id);
314  if (it != starting_positions_.left.end()) {
315  auto& coordinate = it->second;
317  }
318  else {
319  return map_location();
320  }
321 }
322 
324 {
325  return special_location(std::to_string(n));
326 }
327 
328 namespace {
329  bool is_number(const std::string& id) {
330  return std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
331  }
332 }
333 
335 {
336  int res = 0;
337  for (auto pair : starting_positions_) {
338  const std::string& id = pair.left;
339  if (is_number(id)) {
340  res = std::max(res, std::stoi(id));
341  }
342  }
343  return res;
344 }
345 
347 {
348  if(const std::string* locName = is_special_location(loc)) {
349  if(is_number(*locName)) {
350  return std::stoi(*locName);
351  }
352  }
353  return 0;
354 }
355 
356 const std::string* gamemap_base::is_special_location(const map_location& loc) const
357 {
358  auto it = starting_positions_.right.find(loc);
359  return it == starting_positions_.right.end() ? nullptr : &it->second;
360 }
361 
362 void gamemap_base::set_special_location(const std::string& id, const map_location& loc)
363 {
364  bool valid = loc.valid();
365  auto it_left = starting_positions_.left.find(id);
366  if (it_left != starting_positions_.left.end()) {
367  if (valid) {
368  starting_positions_.left.replace_data(it_left, loc);
369  }
370  else {
371  starting_positions_.left.erase(it_left);
372  }
373  }
374  else {
375  starting_positions_.left.insert(it_left, std::pair(id, loc));
376  }
377 }
378 
380 {
381  set_special_location(std::to_string(side), loc);
382 }
383 
384 bool gamemap_base::on_board(const map_location& loc) const
385 {
386  return loc.valid() && loc.x < w() && loc.y < h();
387 }
388 
390 {
391  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
392  loc.x >= -border_size() && loc.x < w() + border_size() &&
393  loc.y >= -border_size() && loc.y < h() + border_size();
394 }
395 
396 void gamemap::set_terrain(const map_location& loc, const t_translation::terrain_code & terrain, const terrain_type_data::merge_mode mode, bool replace_if_failed) {
397  if(!on_board_with_border(loc)) {
398  DBG_G << "set_terrain: " << loc << " is not on the map.";
399  // off the map: ignore request
400  return;
401  }
402 
403  t_translation::terrain_code new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
404 
405  if(new_terrain == t_translation::NONE_TERRAIN) {
406  return;
407  }
408 
409  if(on_board(loc)) {
410  const bool old_village = is_village(loc);
411  const bool new_village = tdata_->is_village(new_terrain);
412 
413  if(old_village && !new_village) {
414  villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end());
415  } else if(!old_village && new_village) {
416  villages_.push_back(loc);
417  }
418  }
419 
420  (*this)[loc] = new_terrain;
421 }
422 
423 std::vector<map_location> gamemap_base::parse_location_range(const std::string &x, const std::string &y,
424  bool with_border) const
425 {
426  std::vector<map_location> res;
427  const std::vector<std::string> xvals = utils::split(x);
428  const std::vector<std::string> yvals = utils::split(y);
429  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
430  if (with_border) {
431  int bs = border_size();
432  xmin -= bs;
433  xmax += bs;
434  ymin -= bs;
435  ymax += bs;
436  }
437 
438  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
439  {
440  std::pair<int,int> xrange, yrange;
441 
442  if (i < xvals.size()) {
443  xrange = utils::parse_range(xvals[i]);
444  if (xrange.first < xmin) xrange.first = xmin;
445  if (xrange.second > xmax) xrange.second = xmax;
446  } else {
447  xrange.first = xmin;
448  xrange.second = xmax;
449  }
450 
451  if (i < yvals.size()) {
452  yrange = utils::parse_range(yvals[i]);
453  if (yrange.first < ymin) yrange.first = ymin;
454  if (yrange.second > ymax) yrange.second = ymax;
455  } else {
456  yrange.first = ymin;
457  yrange.second = ymax;
458  }
459 
460  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
461  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
462  res.emplace_back(x2-1,y2-1);
463  }
464  }
465  }
466  return res;
467 }
468 
469 std::string gamemap_base::to_string() const
470 {
471  return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n";
472 }
473 
474 const std::vector<map_location> gamemap_base::starting_positions() const {
476  std::vector<map_location> res;
477  for(int i = 1; i <= n; i++) {
478  res.push_back(starting_position(i));
479  }
480  return res;
481 }
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:159
static game_config_manager * get()
static game_config_view wrap(const config &cfg)
virtual ~gamemap_base()
Definition: map.cpp:128
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
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:379
terrain_map & tiles()
Definition: map.hpp:157
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:362
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:214
map_location special_location(const std::string &id) const
Definition: map.cpp:311
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:423
map_location starting_position(int side) const
Definition: map.cpp:323
virtual 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)=0
Clobbers over the terrain at location 'loc', with the given terrain.
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:59
std::string to_string() const
Definition: map.cpp:469
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:334
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 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:346
location_map starting_positions_
Definition: map.hpp:161
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:356
int border_size() const
Size of the map border.
Definition: map.hpp:56
const std::vector< map_location > starting_positions() const
Definition: map.cpp:474
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:384
location_map & special_locations()
Definition: map.hpp:90
terrain_map tiles_
Definition: map.hpp:160
const t_translation::ter_list & underlying_union_terrain(const map_location &loc) const
Definition: map.cpp:58
bool is_village(const map_location &loc) const
Definition: map.cpp:65
std::string get_underlying_terrain_string(const t_translation::terrain_code &terrain) const
Definition: map.cpp:86
bool is_castle(const map_location &loc) const
Definition: map.cpp:69
void read(const std::string &data, const bool allow_invalid=true)
Definition: map.cpp:132
const t_translation::ter_list & underlying_mvt_terrain(const map_location &loc) const
Definition: map.cpp:54
const t_translation::ter_list & underlying_def_terrain(const map_location &loc) const
Definition: map.cpp:56
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:43
std::string write() const
Definition: map.cpp:209
std::vector< map_location > villages_
Definition: map.hpp:257
std::string get_terrain_editor_string(const map_location &loc) const
Definition: map.cpp:62
void write_terrain(const map_location &loc, config &cfg) const
Writes the terrain at loc to cfg.
Definition: map.cpp:100
gamemap(const std::string &data)
Loads a map.
Definition: map.cpp:105
bool is_keep(const map_location &loc) const
Definition: map.cpp:71
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:60
std::shared_ptr< terrain_type_data > tdata_
Definition: map.hpp:254
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:97
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
int gives_healing(const map_location &loc) const
Definition: map.cpp:67
int read_header(const std::string &data)
Reads the header of a map which is saved in the deprecated map_data format.
Definition: map.cpp:182
std::size_t i
Definition: function.cpp:968
int w
T end(const std::pair< T, T > &p)
Standard logging facilities (interface).
#define DBG_G
Definition: map.cpp:40
static lg::log_domain log_config("config")
CURSOR_TYPE get()
Definition: cursor.cpp:216
void remove()
Removes a tip.
Definition: tooltip.cpp:109
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::pair< int, int > parse_range(const std::string &str)
Recognises the following patterns, and returns a {min, max} pair.
std::vector< std::string > split(const config_attribute_value &val)
std::string_view data
Definition: picture.cpp:194
std::optional< t_translation::terrain_code > terrain_
Definition: map.hpp:116
terrain_type_data::merge_mode mode_
Definition: map.hpp:115
Encapsulates the map of the game.
Definition: location.hpp:38
bool valid() const
Definition: location.hpp:89
int wml_y() const
Definition: location.hpp:154
static const map_location & null_location()
Definition: location.hpp:81
int wml_x() const
Definition: location.hpp:153
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