The Battle for Wesnoth  1.19.10+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 "game_config_manager.hpp"
25 #include "log.hpp"
26 #include "map/exception.hpp"
28 #include "terrain/terrain.hpp"
29 #include "terrain/type_data.hpp"
30 #include "utils/general.hpp"
31 #include "wml_exception.hpp"
32 
33 #include <algorithm>
34 #include <sstream>
35 #include <utility>
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 
60 std::string gamemap::get_terrain_string(const map_location& loc) const
61  { return get_terrain_string(get_terrain(loc)); }
64 
66  { return on_board(loc) && is_village(get_terrain(loc)); }
68  { return on_board(loc) ? gives_healing(get_terrain(loc)) : 0; }
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); }
82 std::string gamemap::get_terrain_string(const t_translation::terrain_code & terrain) const
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(); }
94 bool gamemap::is_keep(const t_translation::terrain_code & terrain) const
95  { return tdata_->get_terrain_info(terrain).is_keep(); }
96 
98  { return tdata_->get_terrain_info(terrain); }
99 
101 {
103 }
104 
105 gamemap::gamemap(const std::string& data):
106  gamemap_base(1, 1),
107  tdata_(),
108  villages_()
109 {
110  if(const auto* gcm = game_config_manager::get()) {
111  tdata_ = gcm->terrain_types();
112  } else {
113  // Should only be encountered in unit tests
114  tdata_ = std::make_shared<terrain_type_data>(game_config_view::wrap({}));
115  }
116 
117  DBG_G << "loading map: '" << data << "'";
118  read(data);
119 }
120 
122  : tiles_(w, h, t)
123  , starting_positions_()
124 {
125 
126 }
127 
129 {
130 }
131 
132 void gamemap::read(const std::string& data, const bool allow_invalid)
133 {
135  villages_.clear();
136  special_locations().clear();
137 
138  if(data.empty()) {
139  if(allow_invalid) return;
140  }
141 
142  try {
144 
145  } catch(const t_translation::error& e) {
146  // We re-throw the error but as map error.
147  // Since all codepaths test for this, it's the least work.
148  throw incorrect_map_format_error(e.message);
149  }
150 
151  // Post processing on the map
152  VALIDATE((total_width() >= 1 && total_height() >= 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 std::string gamemap::write() const
179 {
181 }
182 
183 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)
184 {
185  int xpos = loc.wml_x();
186  int ypos = loc.wml_y();
187 
188  const int xstart = std::max<int>(0, -xpos);
189  const int xend = std::min<int>(m.total_width(), total_width() - xpos);
190  const int xoffset = xpos;
191 
192  const int ystart_even = std::max<int>(0, -ypos);
193  const int yend_even = std::min<int>(m.total_height(), total_height() - ypos);
194  const int yoffset_even = ypos;
195 
196  const int ystart_odd = std::max<int>(0, -ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
197  const int yend_odd = std::min<int>(m.total_height(), total_height() - ypos +(xpos & 1) -(m_is_odd ? 1 : 0));
198  const int yoffset_odd = ypos -(xpos & 1) + (m_is_odd ? 1 : 0);
199 
200  for(int x1 = xstart; x1 != xend; ++x1) {
201  int ystart, yend, yoffset;
202  if(x1 & 1) {
203  ystart = ystart_odd;
204  yend = yend_odd;
205  yoffset = yoffset_odd;
206  }
207  else {
208  ystart = ystart_even;
209  yend = yend_even;
210  yoffset = yoffset_even;
211  }
212  for(int y1 = ystart; y1 != yend; ++y1) {
213  const int x2 = x1 + xoffset;
214  const int y2 = y1 + yoffset;
215 
216  const t_translation::terrain_code t = m.get_terrain({x1,y1, wml_loc()});
217  const t_translation::terrain_code current = get_terrain({x2, y2, wml_loc()});
218 
220  continue;
221  }
222 
223  // See if there is a matching rule
224  const overlay_rule* rule = nullptr;
225  for(const overlay_rule& current_rule : rules)
226  {
227  if(!current_rule.old_.empty() && !t_translation::terrain_matches(current, current_rule.old_)) {
228  continue;
229  }
230  if(!current_rule.new_.empty() && !t_translation::terrain_matches(t, current_rule.new_)) {
231  continue;
232  }
233  rule = &current_rule;
234  break;
235  }
236 
237  if (!rule) {
239  }
240  else if(!rule->use_old_) {
241  set_terrain(map_location(x2, y2, wml_loc()), rule->terrain_ ? *rule->terrain_ : t , rule->mode_, rule->replace_if_failed_);
242  }
243  }
244  }
245 
246  if (!ignore_special_locations) {
247  for(auto& pair : m.special_locations().left) {
248 
249  int x = pair.second.wml_x();
250  int y = pair.second.wml_y();
251  if(x & 1) {
252  if(x < xstart || x >= xend || y < ystart_odd || y >= yend_odd) {
253  continue;
254  }
255  }
256  else {
257  if(x < xstart || x >= xend || y < ystart_even || y >= yend_even) {
258  continue;
259  }
260  }
261  int x_new = x + xoffset;
262  int y_new = y + ((x & 1 ) ? yoffset_odd : yoffset_even);
263  map_location pos_new = map_location(x_new, y_new, wml_loc());
264 
265  starting_positions_.left.erase(pair.first);
266  starting_positions_.insert(location_map::value_type(pair.first, t_translation::coordinate(pos_new.x, pos_new.y)));
267  }
268  }
269 }
271 {
272 
274  return tiles_.get(loc.x + border_size(), loc.y + border_size());
275  }
276 
278 }
279 
280 map_location gamemap_base::special_location(const std::string& id) const
281 {
282  auto it = starting_positions_.left.find(id);
283  if (it != starting_positions_.left.end()) {
284  auto& coordinate = it->second;
286  }
287  else {
288  return map_location();
289  }
290 }
291 
293 {
294  return special_location(std::to_string(n));
295 }
296 
297 namespace {
298  bool is_number(const std::string& id) {
299  return std::find_if(id.begin(), id.end(), [](char c) { return !std::isdigit(c); }) == id.end();
300  }
301 }
302 
304 {
305  int res = 0;
306  for (auto pair : starting_positions_) {
307  const std::string& id = pair.left;
308  if (is_number(id)) {
309  res = std::max(res, std::stoi(id));
310  }
311  }
312  return res;
313 }
314 
316 {
317  if(const std::string* locName = is_special_location(loc)) {
318  if(is_number(*locName)) {
319  return std::stoi(*locName);
320  }
321  }
322  return 0;
323 }
324 
325 const std::string* gamemap_base::is_special_location(const map_location& loc) const
326 {
327  auto it = starting_positions_.right.find(loc);
328  return it == starting_positions_.right.end() ? nullptr : &it->second;
329 }
330 
331 void gamemap_base::set_special_location(const std::string& id, const map_location& loc)
332 {
333  bool valid = loc.valid();
334  auto it_left = starting_positions_.left.find(id);
335  if (it_left != starting_positions_.left.end()) {
336  if (valid) {
337  starting_positions_.left.replace_data(it_left, loc);
338  }
339  else {
340  starting_positions_.left.erase(it_left);
341  }
342  }
343  else {
344  starting_positions_.left.insert(it_left, std::pair(id, loc));
345  }
346 }
347 
349 {
350  set_special_location(std::to_string(side), loc);
351 }
352 
354 {
355  return loc.valid() && loc.x < w() && loc.y < h();
356 }
357 
359 {
360  return !tiles_.data.empty() && // tiles_ is not empty when initialized.
361  loc.x >= -border_size() && loc.x < w() + border_size() &&
362  loc.y >= -border_size() && loc.y < h() + border_size();
363 }
364 
365 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) {
366  if(!on_board_with_border(loc)) {
367  DBG_G << "set_terrain: " << loc << " is not on the map.";
368  // off the map: ignore request
369  return;
370  }
371 
372  t_translation::terrain_code new_terrain = tdata_->merge_terrains(get_terrain(loc), terrain, mode, replace_if_failed);
373 
374  if(new_terrain == t_translation::NONE_TERRAIN) {
375  return;
376  }
377 
378  if(on_board(loc)) {
379  const bool old_village = is_village(loc);
380  const bool new_village = tdata_->is_village(new_terrain);
381 
382  if(old_village && !new_village) {
384  } else if(!old_village && new_village) {
385  villages_.push_back(loc);
386  }
387  }
388 
389  (*this)[loc] = new_terrain;
390 }
391 
392 std::vector<map_location> gamemap_base::parse_location_range(const std::string &x, const std::string &y,
393  bool with_border) const
394 {
395  std::vector<map_location> res;
396  const std::vector<std::string> xvals = utils::split(x);
397  const std::vector<std::string> yvals = utils::split(y);
398  int xmin = 1, xmax = w(), ymin = 1, ymax = h();
399  if (with_border) {
400  int bs = border_size();
401  xmin -= bs;
402  xmax += bs;
403  ymin -= bs;
404  ymax += bs;
405  }
406 
407  for (unsigned i = 0; i < xvals.size() || i < yvals.size(); ++i)
408  {
409  std::pair<int,int> xrange, yrange;
410 
411  if (i < xvals.size()) {
412  xrange = utils::parse_range(xvals[i]);
413  if (xrange.first < xmin) xrange.first = xmin;
414  if (xrange.second > xmax) xrange.second = xmax;
415  } else {
416  xrange.first = xmin;
417  xrange.second = xmax;
418  }
419 
420  if (i < yvals.size()) {
421  yrange = utils::parse_range(yvals[i]);
422  if (yrange.first < ymin) yrange.first = ymin;
423  if (yrange.second > ymax) yrange.second = ymax;
424  } else {
425  yrange.first = ymin;
426  yrange.second = ymax;
427  }
428 
429  for(int x2 = xrange.first; x2 <= xrange.second; ++x2) {
430  for(int y2 = yrange.first; y2 <= yrange.second; ++y2) {
431  res.emplace_back(x2-1,y2-1);
432  }
433  }
434  }
435  return res;
436 }
437 
438 std::string gamemap_base::to_string() const
439 {
440  return t_translation::write_game_map(tiles_, starting_positions_, { 1, 1 }) + "\n";
441 }
442 
443 const std::vector<map_location> gamemap_base::starting_positions() const {
445  std::vector<map_location> res;
446  for(int i = 1; i <= n; i++) {
447  res.push_back(starting_position(i));
448  }
449  return res;
450 }
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:158
static game_config_manager * get()
static game_config_view wrap(const config &cfg)
virtual ~gamemap_base()
Definition: map.cpp:128
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:270
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:348
terrain_map & tiles()
Definition: map.hpp:157
void set_special_location(const std::string &id, const map_location &loc)
Definition: map.cpp:331
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:183
map_location special_location(const std::string &id) const
Definition: map.cpp:280
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:392
map_location starting_position(int side) const
Definition: map.cpp:292
virtual void 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.
int total_width() const
Real width of the map, including borders.
Definition: map.hpp:59
std::string to_string() const
Definition: map.cpp:438
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:303
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:358
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:315
location_map starting_positions_
Definition: map.hpp:161
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:325
int border_size() const
Size of the map border.
Definition: map.hpp:56
const std::vector< map_location > starting_positions() const
Definition: map.cpp:443
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:353
location_map & special_locations()
Definition: map.hpp:90
terrain_map tiles_
Definition: map.hpp:160
const t_translation::ter_list & underlying_union_terrain(const map_location &loc) const
Definition: map.cpp:58
bool is_village(const map_location &loc) const
Definition: map.cpp:65
std::string get_underlying_terrain_string(const t_translation::terrain_code &terrain) const
Definition: map.cpp:86
bool is_castle(const map_location &loc) const
Definition: map.cpp:69
void read(const std::string &data, const bool allow_invalid=true)
Definition: map.cpp:132
const t_translation::ter_list & underlying_mvt_terrain(const map_location &loc) const
Definition: map.cpp:54
const t_translation::ter_list & underlying_def_terrain(const map_location &loc) const
Definition: map.cpp:56
const t_translation::ter_list & get_terrain_list() const
Gets the list of terrains.
Definition: map.cpp:43
std::string write() const
Definition: map.cpp:178
std::vector< map_location > villages_
Definition: map.hpp:249
std::string get_terrain_editor_string(const map_location &loc) const
Definition: map.cpp:62
void write_terrain(const map_location &loc, config &cfg) const
Writes the terrain at loc to cfg.
Definition: map.cpp:100
gamemap(const std::string &data)
Loads a map.
Definition: map.cpp:105
bool is_keep(const map_location &loc) const
Definition: map.cpp:71
std::string get_terrain_string(const map_location &loc) const
Definition: map.cpp:60
std::shared_ptr< terrain_type_data > tdata_
Definition: map.hpp:246
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:97
void 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:365
int gives_healing(const map_location &loc) const
Definition: map.cpp:67
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1030
int w
T end(const std::pair< T, T > &p)
Standard logging facilities (interface).
#define DBG_G
Definition: map.cpp:40
static lg::log_domain log_config("config")
CURSOR_TYPE get()
Definition: cursor.cpp:218
ter_map read_game_map(std::string_view str, starting_positions &starting_positions, coordinate border_offset)
Reads a gamemap string into a 2D vector.
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
const terrain_code NONE_TERRAIN
Definition: translation.hpp:58
std::size_t erase(Container &container, const Value &value)
Convenience wrapper for using std::remove on a container.
Definition: general.hpp:117
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:154
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)
std::string_view data
Definition: picture.cpp:178
terrain_type_data::merge_mode mode_
Definition: map.hpp:115
utils::optional< t_translation::terrain_code > terrain_
Definition: map.hpp:116
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: location.hpp:110
int wml_y() const
Definition: location.hpp:218
static const map_location & null_location()
Definition: location.hpp:102
int wml_x() const
Definition: location.hpp:217
terrain_code & get(int x, int y)
Definition: translation.hpp:89
std::vector< terrain_code > data
Definition: translation.hpp:92
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