The Battle for Wesnoth  1.19.19+dev
terrain.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 #include "deprecation.hpp"
17 #include "game_version.hpp"
18 #include "gettext.hpp"
19 #include "log.hpp"
20 #include "terrain/terrain.hpp"
21 #include "utils/general.hpp"
22 
23 static lg::log_domain log_config("config");
24 #define ERR_G LOG_STREAM(err, lg::general())
25 #define WRN_G LOG_STREAM(warn, lg::general())
26 #define LOG_G LOG_STREAM(info, lg::general())
27 #define DBG_G LOG_STREAM(debug, lg::general())
28 
29 /**
30  * Insert second vector into first when the terrain _ref^base is encountered
31  */
33 
35  : minimap_image_()
36  , minimap_image_overlay_()
37  , editor_image_()
38  , id_()
39  , name_()
40  , editor_name_()
41  , description_()
42  , help_topic_text_()
43  , number_(t_translation::VOID_TERRAIN)
44  , mvt_type_(1, t_translation::VOID_TERRAIN)
45  , def_type_(1, t_translation::VOID_TERRAIN)
46  , union_type_(1, t_translation::VOID_TERRAIN)
47  , submerge_(0.0)
48  , height_adjust_(0)
49  , submerge_set_(false)
50  , height_adjust_set_(false)
51  , light_modification_(0)
52  , max_light_(0)
53  , min_light_(0)
54  , heals_(0)
55  , income_description_()
56  , income_description_ally_()
57  , income_description_enemy_()
58  , income_description_own_()
59  , editor_group_()
60  , editor_default_base_(t_translation::VOID_TERRAIN)
61  , village_(false)
62  , castle_(false)
63  , keep_(false)
64  , overlay_(false)
65  , combined_(false)
66  , hide_help_(false)
67  , hide_in_editor_(false)
68  , hide_if_impassable_(false)
69 {
70 }
71 
73  : icon_image_(cfg["icon_image"])
74  , minimap_image_(cfg["symbol_image"])
75  , minimap_image_overlay_()
76  , editor_image_(cfg["editor_image"].empty() ? "terrain/" + minimap_image_ + ".png"
77  : "terrain/" + cfg["editor_image"].str() + ".png")
78  , id_(cfg["id"])
79  , name_(cfg["name"].t_str())
80  , editor_name_(cfg["editor_name"].t_str())
81  , description_(cfg["description"].t_str())
82  , help_topic_text_(cfg["help_topic_text"].t_str())
83  , number_(t_translation::read_terrain_code(cfg["string"].str()))
84  , mvt_type_()
85  , def_type_()
86  , union_type_()
87  , submerge_(cfg["submerge"].to_double())
88  , height_adjust_(cfg["unit_height_adjust"].to_int())
89  , submerge_set_(!cfg["submerge"].empty())
90  , height_adjust_set_(!cfg["unit_height_adjust"].empty())
91  , light_modification_(cfg["light"].to_int())
92  , max_light_(cfg["max_light"].to_int(light_modification_))
93  , min_light_(cfg["min_light"].to_int(light_modification_))
94  , heals_(cfg["heals"].to_int())
95  , income_description_()
96  , income_description_ally_()
97  , income_description_enemy_()
98  , income_description_own_()
99  , editor_group_(cfg["editor_group"])
100  , editor_default_base_(t_translation::read_terrain_code(cfg["default_base"].str()))
101  , village_(cfg["gives_income"].to_bool())
102  , castle_(cfg["recruit_onto"].to_bool())
103  , keep_(cfg["recruit_from"].to_bool())
104  , overlay_(number_.base == t_translation::NO_LAYER)
105  , combined_(false)
106  , hide_help_(cfg["hide_help"].to_bool(false))
107  , hide_in_editor_(cfg["hidden"].to_bool(false))
108  , hide_if_impassable_(cfg["hide_if_impassable"].to_bool(false))
109 {
110 /**
111  * @todo reenable these validations. The problem is that all MP
112  * scenarios/campaigns share the same namespace and one rogue scenario
113  * can avoid the player to create a MP game. So every scenario/campaign
114  * should get its own namespace to be safe.
115  */
116 #if 0
118  missing_mandatory_wml_key("terrain_type", "string"));
119  VALIDATE(!minimap_image_.empty(),
120  missing_mandatory_wml_key("terrain_type", "symbol_image", "string",
122  VALIDATE(!name_.empty(),
123  missing_mandatory_wml_key("terrain_type", "name", "string",
125 #endif
126 
127  if(editor_image_.empty()) {
128  editor_image_ = "terrain/" + minimap_image_ + ".png";
129  }
130 
131  if(hide_in_editor_) {
132  editor_image_ = "";
133  }
134 
135  mvt_type_.push_back(number_);
136  def_type_.push_back(number_);
137 
138  const t_translation::ter_list& alias = t_translation::read_list(cfg["aliasof"].str());
139  if(!alias.empty()) {
140  mvt_type_ = alias;
141  def_type_ = alias;
142  }
143 
144  const t_translation::ter_list& mvt_alias = t_translation::read_list(cfg["mvt_alias"].str());
145  if(!mvt_alias.empty()) {
146  mvt_type_ = mvt_alias;
147  }
148 
149  const t_translation::ter_list& def_alias = t_translation::read_list(cfg["def_alias"].str());
150  if(!def_alias.empty()) {
151  def_type_ = def_alias;
152  }
153 
155  union_type_.insert( union_type_.end(), def_type_.begin(), def_type_.end() );
156 
157  // remove + and -
160 
161  // remove doubles
162  std::sort(union_type_.begin(),union_type_.end());
163  union_type_.erase(std::unique(union_type_.begin(), union_type_.end()), union_type_.end());
164 
165  //mouse over message are only shown on villages
166  if(village_) {
167  income_description_ = cfg["income_description"].t_str();
168  if(income_description_.empty()) {
169  income_description_ = _("Village");
170  }
171 
172  income_description_ally_ = cfg["income_description_ally"].t_str();
174  income_description_ally_ = _("Allied village");
175  }
176 
177  income_description_enemy_ = cfg["income_description_enemy"].t_str();
179  income_description_enemy_ = _("Enemy village");
180  }
181 
182  income_description_own_ = cfg["income_description_own"].t_str();
184  income_description_own_ = _("Owned village");
185  }
186  }
187 }
188 
190  : icon_image_()
191  , minimap_image_(base.minimap_image_)
192  , minimap_image_overlay_(overlay.minimap_image_)
193  , editor_image_(base.editor_image_ + "~BLIT(" + overlay.editor_image_ + ")")
194  , id_(base.id_ + "^" + overlay.id_)
195  , name_(overlay.name_)
196  , editor_name_((base.editor_name_.empty() ? base.name_ : base.editor_name_) + " / " + (overlay.editor_name_.empty() ? overlay.name_ : overlay.editor_name_))
197  , description_(overlay.description())
198  , help_topic_text_()
199  , number_(t_translation::terrain_code(base.number_.base, overlay.number_.overlay))
200  , mvt_type_(overlay.mvt_type_)
201  , def_type_(overlay.def_type_)
202  , union_type_()
203  , submerge_(base.submerge_)
204  , height_adjust_(base.height_adjust_)
205  , submerge_set_(base.submerge_set_)
206  , height_adjust_set_(base.height_adjust_set_)
207  , light_modification_(base.light_modification_ + overlay.light_modification_)
208  , max_light_(std::max(base.max_light_, overlay.max_light_))
209  , min_light_(std::min(base.min_light_, overlay.min_light_))
210  , heals_(std::max<int>(base.heals_, overlay.heals_))
211  , income_description_()
212  , income_description_ally_()
213  , income_description_enemy_()
214  , income_description_own_()
215  , editor_group_()
216  , editor_default_base_()
217  , village_(base.village_ || overlay.village_)
218  , castle_(base.castle_ || overlay.castle_)
219  , keep_(base.keep_ || overlay.keep_)
220  , overlay_(false)
221  , combined_(true)
222  , hide_help_(true)
223  , hide_in_editor_(base.hide_in_editor_ || overlay.hide_in_editor_)
224  , hide_if_impassable_(base.hide_if_impassable_ || overlay.hide_if_impassable_)
225 {
226  if(description_.empty()) {
227  description_ = base.description();
228  }
229 
230  if(overlay.height_adjust_set_) {
231  height_adjust_set_ = true;
232  height_adjust_ = overlay.height_adjust_;
233  }
234 
235  if(overlay.submerge_set_) {
236  submerge_set_ = true;
237  submerge_ = overlay.submerge_;
238  }
239 
242 
244  union_type_.insert( union_type_.end(), def_type_.begin(), def_type_.end() );
245 
246  // remove + and -
249 
250  // remove doubles
251  std::sort(union_type_.begin(),union_type_.end());
252  union_type_.erase(std::unique(union_type_.begin(), union_type_.end()), union_type_.end());
253 
254  //mouse over message are only shown on villages
255  if(base.village_) {
260  }
261  else if (overlay.village_) {
262  income_description_ = overlay.income_description_;
263  income_description_ally_ = overlay.income_description_ally_;
264  income_description_enemy_ = overlay.income_description_enemy_;
265  income_description_own_ = overlay.income_description_own_;
266  }
267 }
268 
270  if(overlay_ && has_default_base()) {
272  }
273  return number_;
274 }
275 
276 bool terrain_type::operator==(const terrain_type& other) const {
277  return minimap_image_ == other.minimap_image_
279  && editor_image_ == other.editor_image_
280  && id_ == other.id_
281  && name_.base_str() == other.name_.base_str()
283  && number_ == other.number_
284  && mvt_type_ == other.mvt_type_
285  && def_type_ == other.def_type_
286  && union_type_ == other.union_type_
287  && height_adjust_ == other.height_adjust_
289  && submerge_ == other.submerge_
290  && submerge_set_ == other.submerge_set_
292  && max_light_ == other.max_light_
293  && min_light_ == other.min_light_
294  && heals_ == other.heals_
295  && village_ == other.village_
296  && castle_ == other.castle_
297  && keep_ == other.keep_
298  && combined_ == other.combined_
299  && overlay_ == other.overlay_
301  && hide_in_editor_ == other.hide_in_editor_
302  && hide_help_ == other.hide_help_;
303 }
304 
306 {
307  // Insert second vector into first when the terrain _ref^base is encountered
308 
309  bool revert = (first.front() == t_translation::MINUS ? true : false);
311 
312  for(i = first.begin(); i != first.end(); ++i) {
313  if(*i == t_translation::PLUS) {
314  revert = false;
315  continue;
316  } else if(*i == t_translation::MINUS) {
317  revert = true;
318  continue;
319  }
320 
321  // This only works for a subset of the possible cases, and doesn't work for
322  // worst(best(a,b),c,d) terrain. Part of the reason that it doesn't work is that
323  // movetype.cpp starts with a default value of either UNREACHABLE or zero, which
324  // when inverted would drown out the values in best(a,b). Another part of the reason
325  // is that the insertion of a plus or minus before the base terrain is commented out
326  // in this function.
327 
328  if(*i == t_translation::BASE) {
329  t_translation::ter_list::iterator insert_it = first.erase(i);
330  //if we are in reverse mode, insert PLUS before and MINUS after the base list
331  //so calculation of base aliases will work normal
332  if(revert) {
333 // insert_it = first.insert(insert_it, t_translation::PLUS);
334 // insert_it++;
335  insert_it = first.insert(insert_it, t_translation::MINUS);
336  }
337  else {
338  //else insert PLUS after the base aliases to restore previous "reverse state"
339  insert_it = first.insert(insert_it, t_translation::PLUS);
340  }
341 
342  first.insert(insert_it, second.begin(), second.end());
343 
344  break;
345  }
346  }
347 
348 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
bool empty() const
Definition: tstring.hpp:199
std::string base_str() const
Definition: tstring.hpp:208
bool has_default_base() const
Definition: terrain.hpp:187
bool castle_
Definition: terrain.hpp:266
bool submerge_set_
Definition: terrain.hpp:249
int light_modification_
Definition: terrain.hpp:252
std::string id_
Definition: terrain.hpp:231
t_string income_description_
Definition: terrain.hpp:257
bool overlay_
Definition: terrain.hpp:268
std::string editor_image_
The image used in the editor palette if not defined in WML it will be initialized with the value of m...
Definition: terrain.hpp:230
t_translation::terrain_code editor_default_base_
Definition: terrain.hpp:263
t_translation::terrain_code terrain_with_default_base() const
Return the overlay part of this terrain, on the default_base().
Definition: terrain.cpp:269
int height_adjust_
Definition: terrain.hpp:247
t_translation::ter_list mvt_type_
Definition: terrain.hpp:242
t_string editor_name_
Definition: terrain.hpp:233
std::string minimap_image_overlay_
Definition: terrain.hpp:224
int max_light_
Definition: terrain.hpp:253
t_translation::terrain_code number_
Definition: terrain.hpp:241
t_string income_description_ally_
Definition: terrain.hpp:258
t_translation::ter_list union_type_
Definition: terrain.hpp:244
bool combined_
Definition: terrain.hpp:269
bool hide_help_
Definition: terrain.hpp:270
t_translation::ter_list def_type_
Definition: terrain.hpp:243
t_string description_
Definition: terrain.hpp:234
const t_string & description() const
Definition: terrain.hpp:50
std::string minimap_image_
The image used in the minimap.
Definition: terrain.hpp:223
terrain_type()
Creates an instance for which is_nonnull() returns false.
Definition: terrain.cpp:34
double submerge_
Definition: terrain.hpp:246
t_string income_description_own_
Definition: terrain.hpp:260
int min_light_
Definition: terrain.hpp:254
bool village_
Definition: terrain.hpp:265
bool hide_in_editor_
Definition: terrain.hpp:271
t_string name_
Definition: terrain.hpp:232
bool height_adjust_set_
Definition: terrain.hpp:250
t_string income_description_enemy_
Definition: terrain.hpp:259
bool operator==(const terrain_type &other) const
Returns true if most of the data matches.
Definition: terrain.cpp:276
const config * cfg
std::size_t i
Definition: function.cpp:1031
Interfaces for manipulating version numbers of engine, add-ons, etc.
static std::string _(const char *str)
Definition: gettext.hpp:97
Standard logging facilities (interface).
terrain_code read_terrain_code(std::string_view str, const ter_layer filler)
Reads a single terrain from a string.
constexpr terrain_code NONE_TERRAIN
Definition: translation.hpp:58
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
const terrain_code MINUS
std::vector< terrain_code > ter_list
Definition: translation.hpp:77
const terrain_code BASE
const terrain_code PLUS
ter_list read_list(std::string_view str, const ter_layer filler)
Reads a list of terrains from a string, when reading the.
constexpr ter_layer NO_LAYER
Definition: translation.hpp:40
std::string write_terrain_code(const terrain_code &tcode)
Writes a single terrain code to a string.
std::size_t erase(Container &container, const Value &value)
Convenience wrapper for using std::remove on a container.
Definition: general.hpp:118
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
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
void merge_alias_lists(t_translation::ter_list &first, const t_translation::ter_list &second)
Insert second vector into first when the terrain _ref^base is encountered.
Definition: terrain.cpp:305
static lg::log_domain log_config("config")
std::string missing_mandatory_wml_key(const std::string &section, const std::string &key, const std::string &primary_key, const std::string &primary_value)
Returns a standard message for a missing wml key (attribute).
#define VALIDATE(cond, message)
The macro to use for the validation of WML.