The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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
36static 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)). */
52
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(); }
67 { return on_board(loc) && tdata_->get_terrain_info((*this)[loc]).is_keep(); }
68
69
70/* Forwarded methods of tdata_ */
72 { return tdata_->get_terrain_string(terrain); }
74 { return tdata_->get_terrain_editor_string(terrain); }
77
79 { return tdata_->get_terrain_info(terrain); }
80
85
86gamemap::gamemap(std::string_view data)
87 : gamemap(1, 1, t_translation::NONE_TERRAIN)
88{
89 DBG_G << "loading map: '" << data << "'";
90 read(data);
91}
92
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
109
110void 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
150std::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
176
177void 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
273
275{
276 auto it = starting_positions_.left.find(id);
277 if (it != starting_positions_.left.end()) {
278 auto& coordinate = it->second;
279 return map_location(coordinate.x, coordinate.y);
280 }
281 else {
282 return map_location();
283 }
284}
285
287{
288 return special_location(std::to_string(n));
289}
290
291namespace {
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
319const 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
325void 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
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
396std::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
442std::string gamemap_base::to_string() const
443{
445}
446
447const 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
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
location_map & special_locations()
Definition map.hpp:105
void set_starting_position(int side, const map_location &loc)
Manipulate starting positions of the different sides.
Definition map.cpp:342
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
terrain_map & tiles()
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: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
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
t_string get_terrain_string(const t_translation::terrain_code &terrain) const
Get a formatted terrain name – terrain (underlying terrains)
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 ...
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.
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.
t_string get_terrain_editor_string(const t_translation::terrain_code &terrain) const
const t_translation::ter_list & list() const
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
#define DBG_G
Definition gettext.cpp:36
Standard logging facilities (interface).
static lg::log_domain log_config("config")
T end(const std::pair< T, T > &p)
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
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
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:119
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
std::vector< terrain_code > data
terrain_code & get(int x, int y)
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
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 i
#define e
#define h
#define c