The Battle for Wesnoth  1.17.17+dev
map.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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 << "'";
119  read(data);
120 }
121 
123  : tiles_(w, h, t)
124  , starting_positions_()
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.
153  throw incorrect_map_format_error(e.message);
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.";
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 }
double t
Definition: astarsearch.cpp:65
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
static game_config_manager * get()
static game_config_view wrap(const config &cfg)
virtual ~gamemap_base()
Definition: map.cpp:129
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:302
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:380
terrain_map & tiles()
Definition: map.hpp:157
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:363
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:215
map_location special_location(const std::string &id) const
Definition: map.cpp:312
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:424
map_location starting_position(int side) const
Definition: map.cpp:324
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:470
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:335
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:390
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:347
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:357
int border_size() const
Size of the map border.
Definition: map.hpp:56
const std::vector< map_location > starting_positions() const
Definition: map.cpp:475
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:385
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:59
bool is_village(const map_location &loc) const
Definition: map.cpp:66
std::string get_underlying_terrain_string(const t_translation::terrain_code &terrain) const
Definition: map.cpp:87
bool is_castle(const map_location &loc) const
Definition: map.cpp:70
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
const t_translation::ter_list & underlying_def_terrain(const map_location &loc) const
Definition: map.cpp:57
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:44
std::string write() const
Definition: map.cpp:210
std::vector< map_location > villages_
Definition: map.hpp:257
std::string get_terrain_editor_string(const map_location &loc) const
Definition: map.cpp:63
void write_terrain(const map_location &loc, config &cfg) const
Writes the terrain at loc to cfg.
Definition: map.cpp:101
gamemap(const std::string &data)
Loads a map.
Definition: map.cpp:106
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:61
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:98
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:397
int gives_healing(const map_location &loc) const
Definition: map.cpp:68
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
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:41
static lg::log_domain log_config("config")
CURSOR_TYPE get()
Definition: cursor.cpp:216
void remove()
Removes a tip.
Definition: tooltip.cpp:111
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)
std::vector< std::string > split(const config_attribute_value &val)
std::string_view data
Definition: picture.cpp:199
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