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