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