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 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 "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 
209 void gamemap::overlay(const gamemap& m, map_location loc, const std::vector<overlay_rule>& rules, bool m_is_odd, bool ignore_special_locations)
210 {
211  int xpos = loc.wml_x();
212  int ypos = loc.wml_y();
213 
214  const int xstart = std::max<int>(0, -xpos);
215  const int xend = std::min<int>(m.total_width(), total_width() -xpos);
216  const int xoffset = xpos;
217 
218  const int ystart_even = std::max<int>(0, -ypos);
219  const int yend_even = std::min<int>(m.total_height(), total_height() - ypos);
220  const int yoffset_even = ypos;
221 
222  const int ystart_odd = std::max<int>(0, -ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
223  const int yend_odd = std::min<int>(m.total_height(), total_height() - ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
224  const int yoffset_odd = ypos -(xpos & 1) + (m_is_odd ? 1 : 0);
225 
226  for(int x1 = xstart; x1 != xend; ++x1) {
227  int ystart, yend, yoffset;
228  if(x1 & 1) {
229  ystart = ystart_odd , yend = yend_odd , yoffset = yoffset_odd;
230  }
231  else {
232  ystart = ystart_even, yend = yend_even, yoffset = yoffset_even;
233  }
234  for(int y1 = ystart; y1 != yend; ++y1) {
235  const int x2 = x1 + xoffset;
236  const int y2 = y1 + yoffset;//ypos + ((xpos & 1) && (x1 & 1) ? 1 : 0);
237 
238  const t_translation::terrain_code t = m.tiles_.get(x1,y1);
239  const t_translation::terrain_code current = tiles_.get(x2, y2);
240 
242  continue;
243  }
244 
245  // See if there is a matching rule
246  const overlay_rule* rule = nullptr;
247  for(const overlay_rule& current_rule : rules)
248  {
249  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
250  continue;
251  }
252  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
253  continue;
254  }
255  rule = &current_rule;
256  break;
257  }
258 
259  if (!rule) {
260  set_terrain(map_location(x2, y2, wml_loc()), t);
261  }
262  else if(!rule->use_old_) {
263  set_terrain(map_location(x2, y2, wml_loc()), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
264  }
265  }
266  }
267 
268  if (!ignore_special_locations) {
269  for(auto& pair : m.starting_positions_.left) {
270 
271  int x = pair.second.wml_x();
272  int y = pair.second.wml_y();
273  if(x & 1) {
274  if(x < xstart || x >= xend || y < ystart_odd || y >= yend_odd) {
275  continue;
276  }
277  }
278  else {
279  if(x < xstart || x >= xend || y < ystart_even || y >= yend_even) {
280  continue;
281  }
282  }
283  int x_new = x + xoffset;
284  int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even);
285  map_location pos_new = map_location(x_new, y_new, wml_loc());
286 
287  starting_positions_.left.erase(pair.first);
288  starting_positions_.insert(starting_positions::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y)));
289  }
290  }
291 }
293 {
294 
295  if(on_board_with_border(loc)) {
296  return (*this)[loc];
297  }
298 
300 }
301 
302 map_location gamemap::special_location(const std::string& id) const
303 {
304  auto it = starting_positions_.left.find(id);
305  if (it != starting_positions_.left.end()) {
306  auto& coordinate = it->second;
308  }
309  else {
310  return map_location();
311  }
312 }
313 
315 {
316  return special_location(std::to_string(n));
317 }
318 
320 {
321  int res = 0;
322  for (auto pair : starting_positions_) {
323  const std::string& id = pair.left;
324  bool is_number = std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
325  if (is_number) {
326  res = std::max(res, std::stoi(id));
327  }
328  }
329  return res;
330 }
331 
332 const std::string* gamemap::is_starting_position(const map_location& loc) const
333 {
334  auto it = starting_positions_.right.find(loc);
335  return it == starting_positions_.right.end() ? nullptr : &it->second;
336 }
337 
338 void gamemap::set_special_location(const std::string& id, const map_location& loc)
339 {
340  bool valid = loc.valid();
341  auto it_left = starting_positions_.left.find(id);
342  if (it_left != starting_positions_.left.end()) {
343  if (valid) {
344  starting_positions_.left.replace_data(it_left, loc);
345  }
346  else {
347  starting_positions_.left.erase(it_left);
348  }
349  }
350  else {
351  starting_positions_.left.insert(it_left, std::make_pair(id, loc));
352  }
353 }
354 
356 {
357  set_special_location(std::to_string(side), loc);
358 }
359 
360 bool gamemap::on_board(const map_location& loc) const
361 {
362  return loc.valid() && loc.x < w_ && loc.y < h_;
363 }
364 
366 {
367  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
368  loc.x >= -border_size() && loc.x < w_ + border_size() &&
369  loc.y >= -border_size() && loc.y < h_ + border_size();
370 }
371 
372 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) {
373  if(!on_board_with_border(loc)) {
374  DBG_G << "set_terrain: " << loc << " is not on the map.\n";
375  // off the map: ignore request
376  return;
377  }
378 
379  t_translation::terrain_code new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
380 
381  if(new_terrain == t_translation::NONE_TERRAIN) {
382  return;
383  }
384 
385  if(on_board(loc)) {
386  const bool old_village = is_village(loc);
387  const bool new_village = tdata_->is_village(new_terrain);
388 
389  if(old_village && !new_village) {
390  villages_.erase(std::remove(villages_.begin(),villages_.end(),loc),villages_.end());
391  } else if(!old_village && new_village) {
392  villages_.push_back(loc);
393  }
394  }
395 
396  (*this)[loc] = new_terrain;
397 }
398 
399 std::vector<map_location> gamemap::parse_location_range(const std::string &x, const std::string &y,
400  bool with_border) const
401 {
402  std::vector<map_location> res;
403  const std::vector<std::string> xvals = utils::split(x);
404  const std::vector<std::string> yvals = utils::split(y);
405  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
406  if (with_border) {
407  int bs = border_size();
408  xmin -= bs;
409  xmax += bs;
410  ymin -= bs;
411  ymax += bs;
412  }
413 
414  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
415  {
416  std::pair<int,int> xrange, yrange;
417 
418  if (i < xvals.size()) {
419  xrange = utils::parse_range(xvals[i]);
420  if (xrange.first < xmin) xrange.first = xmin;
421  if (xrange.second > xmax) xrange.second = xmax;
422  } else {
423  xrange.first = xmin;
424  xrange.second = xmax;
425  }
426 
427  if (i < yvals.size()) {
428  yrange = utils::parse_range(yvals[i]);
429  if (yrange.first < ymin) yrange.first = ymin;
430  if (yrange.second > ymax) yrange.second = ymax;
431  } else {
432  yrange.first = ymin;
433  yrange.second = ymax;
434  }
435 
436  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
437  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
438  res.emplace_back(x2-1,y2-1);
439  }
440  }
441  }
442  return res;
443 }
void remove()
Removes a tip.
Definition: tooltip.cpp:188
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
int h() const
Effective map height.
Definition: map.hpp:116
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:59
std::vector< map_location > villages_
Definition: map.hpp:249
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:332
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:42
int border_size() const
Size of the map border.
Definition: map.hpp:119
static lg::log_domain log_config("config")
#define DBG_G
Definition: map.cpp:39
int wml_x() const
Definition: location.hpp:157
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:96
boost::optional< t_translation::terrain_code > terrain_
Definition: map.hpp:93
std::vector< terrain_code > data
Definition: translation.hpp:93
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:360
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.
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:292
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:372
const terrain_code VOID_TERRAIN
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:122
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:399
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:365
int wml_y() const
Definition: location.hpp:158
bool valid() const
Definition: location.hpp:93
Encapsulates the map of the game.
Definition: map.hpp:36
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:338
void overlay(const gamemap &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:209
int num_valid_starting_positions() const
Definition: map.cpp:319
int w_
Sizes of the map area.
Definition: map.hpp:252
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:246
Encapsulates the map of the game.
Definition: location.hpp:42
int w() const
Effective map width.
Definition: map.hpp:113
t_translation::ter_map tiles_
Definition: map.hpp:233
std::size_t i
Definition: function.cpp:933
const ter_data_cache & tdata() const
Definition: map.hpp:69
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:314
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:125
terrain_code & get(int x, int y)
Definition: translation.hpp:90
starting_positions starting_positions_
Definition: map.hpp:235
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
bool replace_if_failed_
Definition: map.hpp:95
Standard logging facilities (interface).
std::vector< terrain_code > ter_list
Definition: translation.hpp:78
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:355
static const map_location & null_location()
Definition: location.hpp:85
std::string message
Definition: exceptions.hpp:31
#define e
terrain_type_data::merge_mode mode_
Definition: map.hpp:92
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:253
virtual ~gamemap()
Definition: map.cpp:116
map_location special_location(const std::string &id) const
Definition: map.cpp:302
std::shared_ptr< terrain_type_data > ter_data_cache