The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 http://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 "log.hpp"
25 #include "map/exception.hpp"
26 #include "serialization/parser.hpp"
28 #include "terrain/terrain.hpp"
29 #include "terrain/type_data.hpp"
30 #include "wml_exception.hpp"
31 
32 #include <algorithm>
33 #include <sstream>
34 #include <utility>
35 
36 #include <boost/optional.hpp>
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)); }
62  { return get_terrain_string(get_terrain(loc)); }
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); }
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(); }
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 ter_data_cache& tdata, const std::string& data):
107  tiles_(1, 1),
108  tdata_(tdata),
109  villages_(),
110  w_(-1),
111  h_(-1)
112 {
113  DBG_G << "loading map: '" << data << "'\n";
114 
115  read(data);
116 }
117 
119 {
120 }
121 
122 void gamemap::read(const std::string& data, const bool allow_invalid)
123 {
125  villages_.clear();
126  starting_positions_.clear();
127 
128  if(data.empty()) {
129  w_ = 0;
130  h_ = 0;
131  if(allow_invalid) return;
132  }
133 
134  int offset = read_header(data);
135 
136  const std::string& data_only = std::string(data, offset);
137 
138  try {
140 
141  } catch(t_translation::error& e) {
142  // We re-throw the error but as map error.
143  // Since all codepaths test for this, it's the least work.
145  }
146 
147  // Post processing on the map
148  w_ = total_width() - 2 * border_size();
149  h_ = total_height() - 2 * border_size();
150  //Disabled since there are callcases which pass along a valid map header but empty
151  //map data. Still, loading (and actually applying) an empty map causes problems later on.
152  //Other callcases which need to load a dummy map use completely empty data :(.
153  //VALIDATE((w_ >= 1 && h_ >= 1), "A map needs at least 1 tile, the map cannot be loaded.");
154 
155  for(int x = 0; x < total_width(); ++x) {
156  for(int y = 0; y < total_height(); ++y) {
157 
158  // Is the terrain valid?
160  if(tdata_->map().count(t) == 0) {
161  if(!tdata_->try_merge_terrains(t)) {
162  std::stringstream ss;
163  ss << "Illegal tile in map: (" << t_translation::write_terrain_code(t)
164  << ") '" << t << "'";
165  throw incorrect_map_format_error(ss.str().c_str());
166  }
167  }
168 
169  // Is it a village?
170  if(x >= border_size() && y >= border_size()
171  && x < total_width()- border_size() && y < total_height()- border_size()
172  && tdata_->is_village(tiles_.get(x, y))) {
173  villages_.push_back(map_location(x - border_size(), y - border_size()));
174  }
175  }
176  }
177 }
178 
180 {
181  // Test whether there is a header section
182  size_t header_offset = data.find("\n\n");
183  if(header_offset == std::string::npos) {
184  // For some reason Windows will fail to load a file with \r\n
185  // lineending properly no problems on Linux with those files.
186  // This workaround fixes the problem the copy later will copy
187  // the second \r\n to the map, but that's no problem.
188  header_offset = data.find("\r\n\r\n");
189  }
190  const size_t comma_offset = data.find(",");
191  // The header shouldn't contain commas, so if the comma is found
192  // before the header, we hit a \n\n inside or after a map.
193  // This is no header, so don't parse it as it would be.
194 
195  if (!(!(header_offset == std::string::npos || comma_offset < header_offset)))
196  return 0;
197 
198  std::string header_str(std::string(data, 0, header_offset + 1));
199  config header;
200  ::read(header, header_str);
201 
202  return header_offset + 2;
203 }
204 
205 
207 {
209 }
210 namespace
211 {
212  struct overlay_rule
213  {
217  boost::optional<t_translation::terrain_code> terrain_;
218  bool use_old_;
219  bool replace_if_failed_;
220 
221  overlay_rule()
222  : old_()
223  , new_()
224  , mode_(terrain_type_data::BOTH)
225  , terrain_()
226  , use_old_(false)
227  , replace_if_failed_(false)
228  {
229 
230  }
231  };
232 }
233 void gamemap::overlay(const gamemap& m, const config& rules_cfg, map_location loc)
234 {
235  int xpos = loc.x;
236  int ypos = loc.y;
237  //const config::const_child_itors &rules = rules_cfg.child_range("rule");
238  std::vector<overlay_rule> rules(rules_cfg.child_count("rule"));
239  for(size_t i = 0; i <rules.size(); ++i)
240  {
241  const config& cfg = rules_cfg.child("rule", i);
242  rules[i].old_ = t_translation::read_list(cfg["old"]);
243  rules[i].new_ = t_translation::read_list(cfg["new"]);
244  rules[i].mode_ = cfg["layer"] == "base" ? terrain_type_data::BASE : cfg["layer"] == "overlay" ? terrain_type_data::OVERLAY : terrain_type_data::BOTH;
246  if(!terrain.empty()) {
247  rules[i].terrain_ = terrain[0];
248  }
249  rules[i].use_old_ = cfg["use_old"].to_bool();
250  rules[i].replace_if_failed_ = cfg["replace_if_failed"].to_bool();
251  }
252 
253  const int xstart = std::max<int>(-border_size(), -xpos - border_size());
254  const int ystart = std::max<int>(-border_size(), -ypos - border_size() - ((xpos & 1) ? 1 : 0));
255  const int xend = std::min<int>(m.w() + border_size(), w() + border_size() - xpos);
256  const int yend = std::min<int>(m.h() + border_size(), h() + border_size() - ypos);
257 
258  for(int x1 = xstart; x1 < xend; ++x1) {
259  for(int y1 = ystart; y1 < yend; ++y1) {
260  const int x2 = x1 + xpos;
261  const int y2 = y1 + ypos + ((xpos & 1) && (x1 & 1) ? 1 : 0);
262 
263  const t_translation::terrain_code t = m[{x1,y1}];
264  const t_translation::terrain_code current = (*this)[{x2, y2}];
265 
267  continue;
268  }
269 
270  // See if there is a matching rule
271  const overlay_rule* rule = nullptr;
272  for(const overlay_rule& current_rule : rules)
273  {
274  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
275  continue;
276  }
277  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
278  continue;
279  }
280  rule = &current_rule;
281  break;
282  }
283 
284  if (!rule) {
285  set_terrain(map_location(x2, y2), t);
286  }
287  else if(!rule->use_old_) {
288  set_terrain(map_location(x2, y2), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
289  }
290  }
291  }
292 
293  if (!rules_cfg["ignore_special_locations"].to_bool(false)) {
294  for(auto& pair : m.starting_positions_.left) {
295  starting_positions_.left.erase(pair.first);
296  starting_positions_.insert(starting_positions::value_type(pair.first, t_translation::coordinate(pair.second.x + xpos, pair.second.y + ypos+ ((xpos & 1) && (pair.second.x & 1) ? 1 : 0))));
297  }
298  }
299 }
300 
302 {
303 
304  if(on_board_with_border(loc)) {
305  return (*this)[loc];
306  }
307 
309 }
310 
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 
329 {
330  int res = 0;
331  for (auto pair : starting_positions_) {
332  const std::string& id = pair.left;
333  bool is_number = std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
334  if (is_number) {
335  res = std::max(res, std::stoi(id));
336  }
337  }
338  return res;
339 }
340 
342 {
343  auto it = starting_positions_.right.find(loc);
344  return it == starting_positions_.right.end() ? nullptr : &it->second;
345 }
346 
348 {
349  bool valid = loc.valid();
350  auto it_left = starting_positions_.left.find(id);
351  if (it_left != starting_positions_.left.end()) {
352  if (valid) {
353  starting_positions_.left.replace_data(it_left, loc);
354  }
355  else {
356  starting_positions_.left.erase(it_left);
357  }
358  }
359  else {
360  starting_positions_.left.insert(it_left, std::make_pair(id, loc));
361  }
362 }
363 
365 {
366  set_special_location(std::to_string(side), loc);
367 }
368 
369 bool gamemap::on_board(const map_location& loc) const
370 {
371  return loc.valid() && loc.x < w_ && loc.y < h_;
372 }
373 
375 {
376  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
377  loc.x >= -border_size() && loc.x < w_ + border_size() &&
378  loc.y >= -border_size() && loc.y < h_ + border_size();
379 }
380 
381 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) {
382  if(!on_board_with_border(loc)) {
383  // off the map: ignore request
384  return;
385  }
386 
387  t_translation::terrain_code new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
388 
389  if(new_terrain == t_translation::NONE_TERRAIN) {
390  return;
391  }
392 
393  if(on_board(loc)) {
394  const bool old_village = is_village(loc);
395  const bool new_village = tdata_->is_village(new_terrain);
396 
397  if(old_village && !new_village) {
398  villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end());
399  } else if(!old_village && new_village) {
400  villages_.push_back(loc);
401  }
402  }
403 
404  (*this)[loc] = new_terrain;
405 }
406 
407 std::vector<map_location> gamemap::parse_location_range(const std::string &x, const std::string &y,
408  bool with_border) const
409 {
410  std::vector<map_location> res;
411  const std::vector<std::string> xvals = utils::split(x);
412  const std::vector<std::string> yvals = utils::split(y);
413  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
414  if (with_border) {
415  int bs = border_size();
416  xmin -= bs;
417  xmax += bs;
418  ymin -= bs;
419  ymax += bs;
420  }
421 
422  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
423  {
424  std::pair<int,int> xrange, yrange;
425 
426  if (i < xvals.size()) {
427  xrange = utils::parse_range(xvals[i]);
428  if (xrange.first < xmin) xrange.first = xmin;
429  if (xrange.second > xmax) xrange.second = xmax;
430  } else {
431  xrange.first = xmin;
432  xrange.second = xmax;
433  }
434 
435  if (i < yvals.size()) {
436  yrange = utils::parse_range(yvals[i]);
437  if (yrange.first < ymin) yrange.first = ymin;
438  if (yrange.second > ymax) yrange.second = ymax;
439  } else {
440  yrange.first = ymin;
441  yrange.second = ymax;
442  }
443 
444  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
445  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
446  res.emplace_back(x2-1,y2-1);
447  }
448  }
449  }
450  return res;
451 }
452 
454 {
455  t_translation::ter_map tiles_new(tiles_.w + 1, tiles_.h + 1);
456  for (int x = 0, x_end = tiles_new.w; x != x_end; ++x) {
457  for (int y = 0, y_end = tiles_new.h; y != y_end; ++y) {
458  tiles_new.get(x, y) = (x == 0 || y == 0) ? t_translation::VOID_TERRAIN : tiles_.get(x - 1, y - 1);
459  }
460  }
461  ++w_;
462  ++h_;
463  tiles_ = tiles_new;
464 }
465 
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:99
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:44
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:374
void remove()
Removes a tip.
Definition: tooltip.cpp:189
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:419
std::pair< int, int > parse_range(const std::string &str)
std::vector< char_t > string
const terrain_code NONE_TERRAIN
Definition: translation.hpp:58
ter_list read_list(const std::string &str, const ter_layer filler)
Reads a list of terrains from a string, when reading the.
std::vector< map_location > villages_
Definition: map.hpp:224
std::string get_terrain_editor_string(const map_location &loc) const
Definition: map.cpp:63
Add a special kind of assert to validate whether the input from WML doesn'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.
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
static lg::log_domain log_config("config")
#define DBG_G
Definition: map.cpp:41
const t_translation::ter_list & underlying_mvt_terrain(const map_location &loc) const
Definition: map.cpp:55
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
std::vector< terrain_code > data
Definition: translation.hpp:95
Definitions for the interface to Wesnoth Markup Language (WML).
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:61
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
void add_fog_border()
Definition: map.cpp:453
int total_height() const
Real height of the map, including borders.
Definition: map.hpp:102
void set_terrain(const map_location &loc, const t_translation::terrain_code &terrain, const terrain_type_data::merge_mode mode=terrain_type_data::BOTH, bool replace_if_failed=false)
Clobbers over the terrain at location 'loc', with the given terrain.
Definition: map.cpp:381
const terrain_code VOID_TERRAIN
t_translation::terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:301
const t_translation::ter_list & underlying_def_terrain(const map_location &loc) const
Definition: map.cpp:57
unsigned child_count(config_key_type key) const
Definition: config.cpp:390
ter_map read_game_map(const std::string &str, starting_positions &starting_positions, coordinate border_offset)
Reads a gamemap string into a 2D vector.
map_location starting_position(int side) const
Definition: map.cpp:323
bool valid() const
Definition: location.hpp:74
const terrain_code FOGGED
std::string get_underlying_terrain_string(const t_translation::terrain_code &terrain) const
Definition: map.cpp:87
int w() const
Effective map width.
Definition: map.hpp:90
Encapsulates the map of the game.
Definition: map.hpp:34
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:347
int border_size() const
Size of the map border.
Definition: map.hpp:96
int w_
Sizes of the map area.
Definition: map.hpp:228
void read(const std::string &data, const bool allow_invalid=true)
Definition: map.cpp:122
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:98
static const map_location & null_location()
Definition: location.hpp:224
static const ::config * terrain
The terrain used to create the cache.
Definition: minimap.cpp:130
std::string write_terrain_code(const terrain_code &tcode)
Writes a single terrain code to a string.
ter_data_cache tdata_
Definition: map.hpp:223
Encapsulates the map of the game.
Definition: location.hpp:42
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:407
std::string write() const
Definition: map.cpp:206
map_location special_location(const std::string &id) const
Definition: map.cpp:311
t_translation::ter_map tiles_
Definition: map.hpp:210
size_t w_
int h() const
Effective map height.
Definition: map.hpp:93
gamemap(const ter_data_cache &tdata, const std::string &data)
Loads a map, with the given terrain configuration.
Definition: map.cpp:106
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.
size_t i
Definition: function.cpp:933
bool is_castle(const map_location &loc) const
Definition: map.cpp:70
void write_terrain(const map_location &loc, config &cfg) const
Writes the terrain at loc to cfg.
Definition: map.cpp:101
terrain_code & get(int x, int y)
Definition: translation.hpp:92
starting_positions starting_positions_
Definition: map.hpp:212
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:369
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:179
double t
Definition: astarsearch.cpp:64
int num_valid_starting_positions() const
Definition: map.cpp:328
Standard logging facilities (interface).
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:364
std::string message
Definition: exceptions.hpp:31
const std::string * 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:341
void overlay(const gamemap &m, const config &rules, map_location loc)
Overlays another map onto this one at the given position.
Definition: map.cpp:233
#define e
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
mock_char c
static map_location::DIRECTION n
int h_
Definition: map.hpp:229
virtual ~gamemap()
Definition: map.cpp:118
bool is_keep(const map_location &loc) const
Definition: map.cpp:72
int gives_healing(const map_location &loc) const
Definition: map.cpp:68
std::shared_ptr< terrain_type_data > ter_data_cache