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