The Battle for Wesnoth  1.19.19+dev
map.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Routines related to game-maps, terrain, locations, directions. etc.
19  */
20 
21 #include "map/map.hpp"
22 
23 #include "config.hpp"
24 #include "log.hpp"
25 #include "map/exception.hpp"
27 #include "terrain/terrain.hpp"
28 #include "terrain/type_data.hpp"
29 #include "utils/general.hpp"
30 #include "wml_exception.hpp"
31 
32 #include <algorithm>
33 #include <sstream>
34 #include <utility>
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 {
51 }
52 
53 std::string gamemap::get_terrain_string(const map_location& loc) const
54  { return get_terrain_string(get_terrain(loc)); }
59 
61  { return on_board(loc) && tdata_->get_terrain_info((*this)[loc]).is_village(); }
63  { return on_board(loc) ? tdata_->get_terrain_info((*this)[loc]).gives_healing() : 0; }
65  { return on_board(loc) && tdata_->get_terrain_info((*this)[loc]).is_castle(); }
66 bool gamemap::is_keep(const map_location& loc) const
67  { return on_board(loc) && tdata_->get_terrain_info((*this)[loc]).is_keep(); }
68 
69 
70 /* Forwarded methods of tdata_ */
71 std::string gamemap::get_terrain_string(const t_translation::terrain_code & terrain) const
72  { return tdata_->get_terrain_string(terrain); }
74  { return tdata_->get_terrain_editor_string(terrain); }
76  { return tdata_->get_underlying_terrain_string(terrain); }
77 
79  { return tdata_->get_terrain_info(terrain); }
80 
82 {
84 }
85 
86 gamemap::gamemap(std::string_view data)
88 {
89  DBG_G << "loading map: '" << data << "'";
90  read(data);
91 }
92 
93 gamemap::gamemap(int w, int h, const terrain_code& t)
94  : gamemap_base(w, h, t)
95  , tdata_(terrain_type_data::get())
96  , villages_()
97 {
98 }
99 
101  : tiles_(w, h, t)
102  , starting_positions_()
103 {
104 }
105 
107 {
108 }
109 
110 void gamemap::read(std::string_view data, const bool allow_invalid)
111 {
113  villages_.clear();
114  special_locations().clear();
115 
116  if(data.empty()) {
117  if(allow_invalid) return;
118  }
119 
120  try {
121  const auto border_offset = t_translation::coordinate{border_size(), border_size()};
123 
124  } catch(const t_translation::error& e) {
125  // We re-throw the error but as map error.
126  // Since all codepaths test for this, it's the least work.
127  throw incorrect_map_format_error(e.message);
128  }
129 
130  // Post processing on the map
131  VALIDATE((total_width() >= 1 && total_height() >= 1), "A map needs at least 1 tile, the map cannot be loaded.");
132 
133  for_each_loc([&](const map_location& loc) {
134  const t_translation::terrain_code& terrain = (*this)[loc];
135 
136  // Is the terrain valid?
137  if(!tdata_->is_known(terrain)) {
138  std::stringstream ss;
139  ss << "Unknown tile in map: (" << t_translation::write_terrain_code(terrain) << ") '" << terrain << "'";
140  throw incorrect_map_format_error(ss.str());
141  }
142 
143  // Note: This excludes village tiles on the border.
144  if(is_village(loc)) {
145  villages_.push_back(loc);
146  }
147  });
148 }
149 
150 std::string_view gamemap::strip_legacy_header(std::string_view data) const
151 {
152  // Test whether there is a header section
153  std::size_t header_offset = data.find("\n\n");
154  if(header_offset == std::string::npos) {
155  // For some reason Windows will fail to load a file with \r\n
156  // lineending properly no problems on Linux with those files.
157  // This workaround fixes the problem the copy later will copy
158  // the second \r\n to the map, but that's no problem.
159  header_offset = data.find("\r\n\r\n");
160  }
161  const std::size_t comma_offset = data.find(",");
162  // The header shouldn't contain commas, so if the comma is found
163  // before the header, we hit a \n\n inside or after a map.
164  // This is no header, so don't parse it as it would be.
165  if(header_offset == std::string::npos || comma_offset < header_offset) {
166  return data;
167  }
168 
169  return data.substr(header_offset + 2);
170 }
171 
172 std::string gamemap::write() const
173 {
175 }
176 
177 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)
178 {
179  int xpos = loc.wml_x();
180  int ypos = loc.wml_y();
181 
182  const int xstart = std::max<int>(0, -xpos);
183  const int xend = std::min<int>(m.total_width(), total_width() - xpos);
184  const int xoffset = xpos;
185 
186  const int ystart_even = std::max<int>(0, -ypos);
187  const int yend_even = std::min<int>(m.total_height(), total_height() - ypos);
188  const int yoffset_even = ypos;
189 
190  const int ystart_odd = std::max<int>(0, -ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
191  const int yend_odd = std::min<int>(m.total_height(), total_height() - ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
192  const int yoffset_odd = ypos -(xpos & 1) + (m_is_odd ? 1 : 0);
193 
194  for(int x1 = xstart; x1 != xend; ++x1) {
195  int ystart, yend, yoffset;
196  if(x1 & 1) {
197  ystart = ystart_odd;
198  yend = yend_odd;
199  yoffset = yoffset_odd;
200  }
201  else {
202  ystart = ystart_even;
203  yend = yend_even;
204  yoffset = yoffset_even;
205  }
206  for(int y1 = ystart; y1 != yend; ++y1) {
207  const int x2 = x1 + xoffset;
208  const int y2 = y1 + yoffset;
209 
210  const t_translation::terrain_code t = m.get_terrain({x1,y1, wml_loc()});
211  const t_translation::terrain_code current = get_terrain({x2, y2, wml_loc()});
212 
214  continue;
215  }
216 
217  // See if there is a matching rule
218  const overlay_rule* rule = nullptr;
219  for(const overlay_rule& current_rule : rules)
220  {
221  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
222  continue;
223  }
224  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
225  continue;
226  }
227  rule = &current_rule;
228  break;
229  }
230 
231  if (!rule) {
233  }
234  else if(!rule->use_old_) {
235  set_terrain(map_location(x2, y2, wml_loc()), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
236  }
237  }
238  }
239 
240  if (!ignore_special_locations) {
241  for(auto& pair : m.special_locations().left) {
242 
243  int x = pair.second.wml_x();
244  int y = pair.second.wml_y();
245  if(x & 1) {
246  if(x < xstart || x >= xend || y < ystart_odd || y >= yend_odd) {
247  continue;
248  }
249  }
250  else {
251  if(x < xstart || x >= xend || y < ystart_even || y >= yend_even) {
252  continue;
253  }
254  }
255  int x_new = x + xoffset;
256  int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even);
257  map_location pos_new = map_location(x_new, y_new, wml_loc());
258 
259  starting_positions_.left.erase(pair.first);
260  starting_positions_.insert(location_map::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y)));
261  }
262  }
263 }
264 
266 {
268  return tiles_.get(loc.x + border_size(), loc.y + border_size());
269  }
270 
272 }
273 
274 map_location gamemap_base::special_location(const std::string& id) const
275 {
276  auto it = starting_positions_.left.find(id);
277  if (it != starting_positions_.left.end()) {
278  auto& coordinate = it->second;
280  }
281  else {
282  return map_location();
283  }
284 }
285 
287 {
288  return special_location(std::to_string(n));
289 }
290 
291 namespace {
292  bool is_number(const std::string& id) {
293  return std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
294  }
295 }
296 
298 {
299  int res = 0;
300  for (auto pair : starting_positions_) {
301  const std::string& id = pair.left;
302  if (is_number(id)) {
303  res = std::max(res, std::stoi(id));
304  }
305  }
306  return res;
307 }
308 
310 {
311  if(const std::string* locName = is_special_location(loc)) {
312  if(is_number(*locName)) {
313  return std::stoi(*locName);
314  }
315  }
316  return 0;
317 }
318 
319 const std::string* gamemap_base::is_special_location(const map_location& loc) const
320 {
321  auto it = starting_positions_.right.find(loc);
322  return it == starting_positions_.right.end() ? nullptr : &it->second;
323 }
324 
325 void gamemap_base::set_special_location(const std::string& id, const map_location& loc)
326 {
327  bool valid = loc.valid();
328  auto it_left = starting_positions_.left.find(id);
329  if (it_left != starting_positions_.left.end()) {
330  if (valid) {
331  starting_positions_.left.replace_data(it_left, loc);
332  }
333  else {
334  starting_positions_.left.erase(it_left);
335  }
336  }
337  else {
338  starting_positions_.left.insert(it_left, std::pair(id, loc));
339  }
340 }
341 
343 {
344  set_special_location(std::to_string(side), loc);
345 }
346 
348 {
349  return loc.valid() && loc.x < w() && loc.y < h();
350 }
351 
353 {
354  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
355  loc.x >= -border_size() && loc.x < w() + border_size() &&
356  loc.y >= -border_size() && loc.y < h() + border_size();
357 }
358 
360  const t_translation::terrain_code& terrain,
362  bool replace_if_failed)
363 {
365  auto& [new_terrain, village_state] = res;
366 
367  // off the map: ignore request
368  if(!on_board_with_border(loc)) {
369  DBG_G << "set_terrain: " << loc << " is not on the map.";
370  return res;
371  }
372 
373  new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
374 
375  if(new_terrain == t_translation::NONE_TERRAIN) {
376  return res;
377  }
378 
379  if(on_board(loc)) {
380  const bool old_village = is_village(loc);
381  const bool new_village = tdata_->is_village(new_terrain);
382 
383  if(old_village && !new_village) {
386  } else if(!old_village && new_village) {
387  villages_.push_back(loc);
389  }
390  }
391 
392  (*this)[loc] = new_terrain;
393  return res;
394 }
395 
396 std::vector<map_location> gamemap_base::parse_location_range(const std::string &x, const std::string &y,
397  bool with_border) const
398 {
399  std::vector<map_location> res;
400  const std::vector<std::string> xvals = utils::split(x);
401  const std::vector<std::string> yvals = utils::split(y);
402  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
403  if (with_border) {
404  int bs = border_size();
405  xmin -= bs;
406  xmax += bs;
407  ymin -= bs;
408  ymax += bs;
409  }
410 
411  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
412  {
413  std::pair<int,int> xrange, yrange;
414 
415  if (i < xvals.size()) {
416  xrange = utils::parse_range(xvals[i]);
417  if (xrange.first < xmin) xrange.first = xmin;
418  if (xrange.second > xmax) xrange.second = xmax;
419  } else {
420  xrange.first = xmin;
421  xrange.second = xmax;
422  }
423 
424  if (i < yvals.size()) {
425  yrange = utils::parse_range(yvals[i]);
426  if (yrange.first < ymin) yrange.first = ymin;
427  if (yrange.second > ymax) yrange.second = ymax;
428  } else {
429  yrange.first = ymin;
430  yrange.second = ymax;
431  }
432 
433  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
434  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
435  res.emplace_back(x2-1,y2-1);
436  }
437  }
438  }
439  return res;
440 }
441 
442 std::string gamemap_base::to_string() const
443 {
444  return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n";
445 }
446 
447 const std::vector<map_location> gamemap_base::starting_positions() const {
449  std::vector<map_location> res;
450  for(int i = 1; i <= n; i++) {
451  res.push_back(starting_position(i));
452  }
453  return res;
454 }
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
virtual ~gamemap_base()
Definition: map.cpp:106
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:265
int w() const
Effective map width.
Definition: map.hpp:50
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition: map.cpp:342
terrain_map & tiles()
Definition: map.hpp:161
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:325
int h() const
Effective map height.
Definition: map.hpp:53
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:177
map_location special_location(const std::string &id) const
Definition: map.cpp:274
gamemap_base()=default
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:396
map_location starting_position(int side) const
Definition: map.cpp:286
void for_each_loc(const F &f) const
Definition: map.hpp:140
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:59
std::string to_string() const
Definition: map.cpp:442
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:297
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:352
int total_height() const
Real height of the map, including borders.
Definition: map.hpp:62
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:309
virtual set_terrain_result 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 'loc', with the given terrain.
location_map starting_positions_
Definition: map.hpp:165
village_state
What happens to a village hex when its terrain is changed.
Definition: map.hpp:80
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:319
int border_size() const
Size of the map border.
Definition: map.hpp:56
const std::vector< map_location > starting_positions() const
Definition: map.cpp:447
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:347
location_map & special_locations()
Definition: map.hpp:105
terrain_map tiles_
Definition: map.hpp:164
Encapsulates the map of the game.
Definition: map.hpp:176
bool is_village(const map_location &loc) const
Definition: map.cpp:60
bool is_castle(const map_location &loc) const
Definition: map.cpp:64
std::string get_underlying_terrain_string(const map_location &loc) const
Definition: map.cpp:57
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:42
std::string write() const
Definition: map.cpp:172
std::vector< map_location > villages_
Definition: map.hpp:252
std::string get_terrain_editor_string(const map_location &loc) const
Definition: map.cpp:55
void write_terrain(const map_location &loc, config &cfg) const
Writes the terrain at loc to cfg.
Definition: map.cpp:81
std::string_view strip_legacy_header(std::string_view data) const
Returns a subview of data which excludes any legacy headers.
Definition: map.cpp:150
bool is_keep(const map_location &loc) const
Definition: map.cpp:66
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:53
gamemap_base::set_terrain_result 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 'loc', with the given terrain.
Definition: map.cpp:359
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:78
gamemap(std::string_view data)
Loads a map.
Definition: map.cpp:86
void read(std::string_view data, const bool allow_invalid=true)
Definition: map.cpp:110
terrain_type_data * tdata_
Definition: map.hpp:249
int gives_healing(const map_location &loc) const
Definition: map.cpp:62
Contains the database of all known terrain types, both those defined explicitly by WML [terrain_type]...
Definition: type_data.hpp:41
t_string get_underlying_terrain_string(const t_translation::terrain_code &terrain) const
Definition: type_data.cpp:162
t_string get_terrain_string(const t_translation::terrain_code &terrain) const
Get a formatted terrain name – terrain (underlying terrains)
Definition: type_data.cpp:135
bool is_village(const t_translation::terrain_code &terrain) const
Definition: type_data.hpp:92
bool is_known(const t_translation::terrain_code &terrain) const
Returns true if get_terrain_info(terrain) would succeed, or false if get_terrain_info(terrain) would ...
Definition: type_data.cpp:209
t_translation::terrain_code merge_terrains(const t_translation::terrain_code &old_t, const t_translation::terrain_code &new_t, const merge_mode mode, bool replace_if_failed=false) const
Tries to find a new terrain which is the combination of old and new terrain using the merge_settings.
Definition: type_data.cpp:217
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Get the corresponding terrain_type information object for a given type of terrain.
Definition: type_data.cpp:123
t_string get_terrain_editor_string(const t_translation::terrain_code &terrain) const
Definition: type_data.cpp:145
const t_translation::ter_list & list() const
Definition: type_data.cpp:110
bool is_keep() const
Definition: terrain.hpp:153
bool is_castle() const
Definition: terrain.hpp:152
bool is_village() const
Definition: terrain.hpp:151
int gives_healing() const
Definition: terrain.hpp:150
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
std::size_t i
Definition: function.cpp:1032
T end(const std::pair< T, T > &p)
Standard logging facilities (interface).
#define DBG_G
Definition: map.cpp:39
static lg::log_domain log_config("config")
CURSOR_TYPE get()
Definition: cursor.cpp:206
ter_map read_game_map(std::string_view str, starting_positions &starting_positions, coordinate border_offset)
Reads a gamemap string into a 2D vector.
constexpr terrain_code NONE_TERRAIN
Definition: translation.hpp:58
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.
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
bool terrain_matches(const terrain_code &src, const terrain_code &dest)
Tests whether a specific terrain matches an expression, for matching rules see above.
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
std::string write_terrain_code(const terrain_code &tcode)
Writes a single terrain code to a string.
const terrain_code FOGGED
std::size_t erase(Container &container, const Value &value)
Convenience wrapper for using std::remove on a container.
Definition: general.hpp:118
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:156
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
Definition: general.hpp:151
std::pair< int, int > parse_range(std::string_view str)
Recognises the following patterns, and returns a {min, max} pair.
std::vector< std::string > split(const config_attribute_value &val)
int w
Definition: pathfind.cpp:188
std::string_view data
Definition: picture.cpp:188
terrain_type_data::merge_mode mode_
Definition: map.hpp:130
utils::optional< t_translation::terrain_code > terrain_
Definition: map.hpp:131
Encapsulates the map of the game.
Definition: location.hpp:46
bool valid() const
Definition: location.hpp:111
int wml_y() const
Definition: location.hpp:187
int wml_x() const
Definition: location.hpp:186
terrain_code & get(int x, int y)
Definition: translation.hpp:83
std::vector< terrain_code > data
Definition: translation.hpp:86
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
mock_char c
static map_location::direction n
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
#define e
#define h