The Battle for Wesnoth  1.15.13+dev
map.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Routines related to game-maps, terrain, locations, directions. etc.
18  */
19 
20 #include "map/map.hpp"
21 
22 #include "config.hpp"
23 #include "formula/string_utils.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 << "'\n";
118  read(data);
119 }
120 
122  : tiles_(w, h, t)
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.
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 , yend = yend_odd , yoffset = yoffset_odd;
235  }
236  else {
237  ystart = ystart_even, yend = yend_even, yoffset = yoffset_even;
238  }
239  for(int y1 = ystart; y1 != yend; ++y1) {
240  const int x2 = x1 + xoffset;
241  const int y2 = y1 + yoffset;
242 
243  const t_translation::terrain_code t = m.get_terrain({x1,y1, wml_loc()});
244  const t_translation::terrain_code current = get_terrain({x2, y2, wml_loc()});
245 
247  continue;
248  }
249 
250  // See if there is a matching rule
251  const overlay_rule* rule = nullptr;
252  for(const overlay_rule& current_rule : rules)
253  {
254  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
255  continue;
256  }
257  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
258  continue;
259  }
260  rule = &current_rule;
261  break;
262  }
263 
264  if (!rule) {
266  }
267  else if(!rule->use_old_) {
268  set_terrain(map_location(x2, y2, wml_loc()), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
269  }
270  }
271  }
272 
273  if (!ignore_special_locations) {
274  for(auto& pair : m.special_locations().left) {
275 
276  int x = pair.second.wml_x();
277  int y = pair.second.wml_y();
278  if(x & 1) {
279  if(x < xstart || x >= xend || y < ystart_odd || y >= yend_odd) {
280  continue;
281  }
282  }
283  else {
284  if(x < xstart || x >= xend || y < ystart_even || y >= yend_even) {
285  continue;
286  }
287  }
288  int x_new = x + xoffset;
289  int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even);
290  map_location pos_new = map_location(x_new, y_new, wml_loc());
291 
292  starting_positions_.left.erase(pair.first);
293  starting_positions_.insert(location_map::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y)));
294  }
295  }
296 }
298 {
299 
300  if(on_board_with_border(loc)) {
301  return tiles_.get(loc.x + border_size(), loc.y + border_size());
302  }
303 
305 }
306 
307 map_location gamemap_base::special_location(const std::string& id) const
308 {
309  auto it = starting_positions_.left.find(id);
310  if (it != starting_positions_.left.end()) {
311  auto& coordinate = it->second;
313  }
314  else {
315  return map_location();
316  }
317 }
318 
320 {
321  return special_location(std::to_string(n));
322 }
323 
324 namespace {
325  bool is_number(const std::string& id) {
326  return std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
327  }
328 }
329 
331 {
332  int res = 0;
333  for (auto pair : starting_positions_) {
334  const std::string& id = pair.left;
335  if (is_number(id)) {
336  res = std::max(res, std::stoi(id));
337  }
338  }
339  return res;
340 }
341 
343 {
344  if(const std::string* locName = is_special_location(loc)) {
345  if(is_number(*locName)) {
346  return std::stoi(*locName);
347  }
348  }
349  return 0;
350 }
351 
352 const std::string* gamemap_base::is_special_location(const map_location& loc) const
353 {
354  auto it = starting_positions_.right.find(loc);
355  return it == starting_positions_.right.end() ? nullptr : &it->second;
356 }
357 
358 void gamemap_base::set_special_location(const std::string& id, const map_location& loc)
359 {
360  bool valid = loc.valid();
361  auto it_left = starting_positions_.left.find(id);
362  if (it_left != starting_positions_.left.end()) {
363  if (valid) {
364  starting_positions_.left.replace_data(it_left, loc);
365  }
366  else {
367  starting_positions_.left.erase(it_left);
368  }
369  }
370  else {
371  starting_positions_.left.insert(it_left, std::pair(id, loc));
372  }
373 }
374 
376 {
377  set_special_location(std::to_string(side), loc);
378 }
379 
380 bool gamemap_base::on_board(const map_location& loc) const
381 {
382  return loc.valid() && loc.x < w() && loc.y < h();
383 }
384 
386 {
387  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
388  loc.x >= -border_size() && loc.x < w() + border_size() &&
389  loc.y >= -border_size() && loc.y < h() + border_size();
390 }
391 
392 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) {
393  if(!on_board_with_border(loc)) {
394  DBG_G << "set_terrain: " << loc << " is not on the map.\n";
395  // off the map: ignore request
396  return;
397  }
398 
399  t_translation::terrain_code new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
400 
401  if(new_terrain == t_translation::NONE_TERRAIN) {
402  return;
403  }
404 
405  if(on_board(loc)) {
406  const bool old_village = is_village(loc);
407  const bool new_village = tdata_->is_village(new_terrain);
408 
409  if(old_village && !new_village) {
410  villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end());
411  } else if(!old_village && new_village) {
412  villages_.push_back(loc);
413  }
414  }
415 
416  (*this)[loc] = new_terrain;
417 }
418 
419 std::vector<map_location> gamemap_base::parse_location_range(const std::string &x, const std::string &y,
420  bool with_border) const
421 {
422  std::vector<map_location> res;
423  const std::vector<std::string> xvals = utils::split(x);
424  const std::vector<std::string> yvals = utils::split(y);
425  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
426  if (with_border) {
427  int bs = border_size();
428  xmin -= bs;
429  xmax += bs;
430  ymin -= bs;
431  ymax += bs;
432  }
433 
434  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
435  {
436  std::pair<int,int> xrange, yrange;
437 
438  if (i < xvals.size()) {
439  xrange = utils::parse_range(xvals[i]);
440  if (xrange.first < xmin) xrange.first = xmin;
441  if (xrange.second > xmax) xrange.second = xmax;
442  } else {
443  xrange.first = xmin;
444  xrange.second = xmax;
445  }
446 
447  if (i < yvals.size()) {
448  yrange = utils::parse_range(yvals[i]);
449  if (yrange.first < ymin) yrange.first = ymin;
450  if (yrange.second > ymax) yrange.second = ymax;
451  } else {
452  yrange.first = ymin;
453  yrange.second = ymax;
454  }
455 
456  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
457  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
458  res.emplace_back(x2-1,y2-1);
459  }
460  }
461  }
462  return res;
463 }
464 
465 std::string gamemap_base::to_string() const
466 {
467  return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n";
468 }
469 
470 const std::vector<map_location> gamemap_base::starting_positions() const {
472  std::vector<map_location> res;
473  for(int i = 1; i <= n; i++) {
474  res.push_back(starting_position(i));
475  }
476  return res;
477 }
void remove()
Removes a tip.
Definition: tooltip.cpp:174
bool is_keep(const map_location &loc) const
Definition: map.cpp:71
const t_translation::ter_list & underlying_union_terrain(const map_location &loc) const
Definition: map.cpp:58
const t_translation::ter_list & underlying_def_terrain(const map_location &loc) const
Definition: map.cpp:56
std::pair< int, int > parse_range(const std::string &str)
const terrain_code NONE_TERRAIN
Definition: translation.hpp:58
location_map & special_locations()
Definition: map.hpp:89
std::vector< map_location > villages_
Definition: map.hpp:256
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:392
void write_terrain(const map_location &loc, config &cfg) const
Writes the terrain at loc to cfg.
Definition: map.cpp:100
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:352
bool is_castle(const map_location &loc) const
Definition: map.cpp:69
map_location starting_position(int side) const
Definition: map.cpp:319
location_map starting_positions_
Definition: map.hpp:160
std::string get_underlying_terrain_string(const t_translation::terrain_code &terrain) const
Definition: map.cpp:86
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:342
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:43
gamemap_base()=default
static lg::log_domain log_config("config")
#define DBG_G
Definition: map.cpp:40
std::shared_ptr< terrain_type_data > tdata_
Definition: map.hpp:253
int wml_x() const
Definition: location.hpp:152
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
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:97
std::vector< terrain_code > data
Definition: translation.hpp:92
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:307
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:358
const std::vector< map_location > starting_positions() const
Definition: map.cpp:470
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:60
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
terrain_map & tiles()
Definition: map.hpp:156
std::optional< t_translation::terrain_code > terrain_
Definition: map.hpp:115
std::string write() const
Definition: map.cpp:209
static game_config_manager * get()
int gives_healing(const map_location &loc) const
Definition: map.cpp:67
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
int w() const
Effective map width.
Definition: map.hpp:49
const terrain_code FOGGED
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:626
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:297
int wml_y() const
Definition: location.hpp:153
terrain_type_data::merge_mode mode_
Definition: map.hpp:114
bool valid() const
Definition: location.hpp:88
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:385
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:375
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
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:37
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:58
std::size_t i
Definition: function.cpp:940
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:330
std::string get_terrain_editor_string(const map_location &loc) const
Definition: map.cpp:62
gamemap(const std::string &data)
Loads a map.
Definition: map.cpp:105
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:380
terrain_code & get(int x, int y)
Definition: translation.hpp:89
int total_height() const
Real height of the map, including borders.
Definition: map.hpp:61
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:159
bool is_village(const map_location &loc) const
Definition: map.cpp:65
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
double t
Definition: astarsearch.cpp:64
std::vector< std::string > split(const config_attribute_value &val)
int border_size() const
Size of the map border.
Definition: map.hpp:55
Standard logging facilities (interface).
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
static const map_location & null_location()
Definition: location.hpp:80
std::string message
Definition: exceptions.hpp:29
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:419
#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:214
virtual ~gamemap_base()
Definition: map.cpp:128
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
mock_char c
static map_location::DIRECTION n
int h() const
Effective map height.
Definition: map.hpp:52
std::string to_string() const
Definition: map.cpp:465