The Battle for Wesnoth  1.19.16+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  , vision_type_(1, t_translation::VOID_TERRAIN)
46  , def_type_(1, t_translation::VOID_TERRAIN)
47  , union_type_(1, t_translation::VOID_TERRAIN)
48  , height_adjust_(0)
49  , height_adjust_set_(false)
50  , submerge_(0.0)
51  , submerge_set_(false)
52  , light_modification_(0)
53  , max_light_(0)
54  , min_light_(0)
55  , heals_(0)
56  , income_description_()
57  , income_description_ally_()
58  , income_description_enemy_()
59  , income_description_own_()
60  , editor_group_()
61  , village_(false)
62  , castle_(false)
63  , keep_(false)
64  , overlay_(false)
65  , combined_(false)
66  , editor_default_base_(t_translation::VOID_TERRAIN)
67  , hide_help_(false)
68  , hide_in_editor_(false)
69  , hide_if_impassable_(false)
70 {
71 }
72 
74  : icon_image_(cfg["icon_image"])
75  , minimap_image_(cfg["symbol_image"])
76  , minimap_image_overlay_()
77  , editor_image_(cfg["editor_image"].empty() ? "terrain/" + minimap_image_ + ".png"
78  : "terrain/" + cfg["editor_image"].str() + ".png")
79  , id_(cfg["id"])
80  , name_(cfg["name"].t_str())
81  , editor_name_(cfg["editor_name"].t_str())
82  , description_(cfg["description"].t_str())
83  , help_topic_text_(cfg["help_topic_text"].t_str())
84  , number_(t_translation::read_terrain_code(cfg["string"].str()))
85  , mvt_type_()
86  , vision_type_()
87  , def_type_()
88  , union_type_()
89  , height_adjust_(cfg["unit_height_adjust"].to_int())
90  , height_adjust_set_(!cfg["unit_height_adjust"].empty())
91  , submerge_(cfg["submerge"].to_double())
92  , submerge_set_(!cfg["submerge"].empty())
93  , light_modification_(cfg["light"].to_int())
94  , max_light_(cfg["max_light"].to_int(light_modification_))
95  , min_light_(cfg["min_light"].to_int(light_modification_))
96  , heals_(cfg["heals"].to_int())
97  , income_description_()
98  , income_description_ally_()
99  , income_description_enemy_()
100  , income_description_own_()
101  , editor_group_(cfg["editor_group"])
102  , village_(cfg["gives_income"].to_bool())
103  , castle_(cfg["recruit_onto"].to_bool())
104  , keep_(cfg["recruit_from"].to_bool())
105  , overlay_(number_.base == t_translation::NO_LAYER)
106  , combined_(false)
107  , editor_default_base_(t_translation::read_terrain_code(cfg["default_base"].str()))
108  , hide_help_(cfg["hide_help"].to_bool(false))
109  , hide_in_editor_(cfg["hidden"].to_bool(false))
110  , hide_if_impassable_(cfg["hide_if_impassable"].to_bool(false))
111 {
112 /**
113  * @todo reenable these validations. The problem is that all MP
114  * scenarios/campaigns share the same namespace and one rogue scenario
115  * can avoid the player to create a MP game. So every scenario/campaign
116  * should get its own namespace to be safe.
117  */
118 #if 0
120  missing_mandatory_wml_key("terrain_type", "string"));
121  VALIDATE(!minimap_image_.empty(),
122  missing_mandatory_wml_key("terrain_type", "symbol_image", "string",
124  VALIDATE(!name_.empty(),
125  missing_mandatory_wml_key("terrain_type", "name", "string",
127 #endif
128 
129  if(editor_image_.empty()) {
130  editor_image_ = "terrain/" + minimap_image_ + ".png";
131  }
132 
133  if(hide_in_editor_) {
134  editor_image_ = "";
135  }
136 
137  mvt_type_.push_back(number_);
138  def_type_.push_back(number_);
139  vision_type_.push_back(number_);
140 
141  const t_translation::ter_list& alias = t_translation::read_list(cfg["aliasof"].str());
142  if(!alias.empty()) {
143  mvt_type_ = alias;
144  vision_type_ = alias;
145  def_type_ = alias;
146  }
147 
148  const t_translation::ter_list& mvt_alias = t_translation::read_list(cfg["mvt_alias"].str());
149  if(!mvt_alias.empty()) {
150  mvt_type_ = mvt_alias;
151  }
152 
153  const t_translation::ter_list& def_alias = t_translation::read_list(cfg["def_alias"].str());
154  if(!def_alias.empty()) {
155  def_type_ = def_alias;
156  }
157 
158  const t_translation::ter_list& vision_alias = t_translation::read_list(cfg["vision_alias"].str());
159  if(!vision_alias.empty()) {
160  // Vision costs are calculated in movetype.cpp, but they're calculated based on gamemap::underlying_mvt_terrain().
161  // Having vision costs that are different to movement costs is still supported, but having separate aliases seems
162  // an edge case that shouldn't be introduced until we're ready to test it.
163  deprecated_message("vision_alias", DEP_LEVEL::REMOVED, {1, 15, 2}, "vision_alias was never completely implemented, vision is calculated using mvt_alias instead");
164  vision_type_ = vision_alias;
165  }
166 
168  union_type_.insert( union_type_.end(), def_type_.begin(), def_type_.end() );
169  union_type_.insert( union_type_.end(), vision_type_.begin(), vision_type_.end() );
170 
171  // remove + and -
174 
175  // remove doubles
176  std::sort(union_type_.begin(),union_type_.end());
177  union_type_.erase(std::unique(union_type_.begin(), union_type_.end()), union_type_.end());
178 
179  //mouse over message are only shown on villages
180  if(village_) {
181  income_description_ = cfg["income_description"].t_str();
182  if(income_description_.empty()) {
183  income_description_ = _("Village");
184  }
185 
186  income_description_ally_ = cfg["income_description_ally"].t_str();
188  income_description_ally_ = _("Allied village");
189  }
190 
191  income_description_enemy_ = cfg["income_description_enemy"].t_str();
193  income_description_enemy_ = _("Enemy village");
194  }
195 
196  income_description_own_ = cfg["income_description_own"].t_str();
198  income_description_own_ = _("Owned village");
199  }
200  }
201 }
202 
204  : icon_image_()
205  , minimap_image_(base.minimap_image_)
206  , minimap_image_overlay_(overlay.minimap_image_)
207  , editor_image_(base.editor_image_ + "~BLIT(" + overlay.editor_image_ + ")")
208  , id_(base.id_ + "^" + overlay.id_)
209  , name_(overlay.name_)
210  , editor_name_((base.editor_name_.empty() ? base.name_ : base.editor_name_) + " / " + (overlay.editor_name_.empty() ? overlay.name_ : overlay.editor_name_))
211  , description_(overlay.description())
212  , help_topic_text_()
213  , number_(t_translation::terrain_code(base.number_.base, overlay.number_.overlay))
214  , mvt_type_(overlay.mvt_type_)
215  , vision_type_(overlay.vision_type_)
216  , def_type_(overlay.def_type_)
217  , union_type_()
218  , height_adjust_(base.height_adjust_)
219  , height_adjust_set_(base.height_adjust_set_)
220  , submerge_(base.submerge_)
221  , submerge_set_(base.submerge_set_)
222  , light_modification_(base.light_modification_ + overlay.light_modification_)
223  , max_light_(std::max(base.max_light_, overlay.max_light_))
224  , min_light_(std::min(base.min_light_, overlay.min_light_))
225  , heals_(std::max<int>(base.heals_, overlay.heals_))
226  , income_description_()
227  , income_description_ally_()
228  , income_description_enemy_()
229  , income_description_own_()
230  , editor_group_()
231  , village_(base.village_ || overlay.village_)
232  , castle_(base.castle_ || overlay.castle_)
233  , keep_(base.keep_ || overlay.keep_)
234  , overlay_(false)
235  , combined_(true)
236  , editor_default_base_()
237  , hide_help_(true)
238  , hide_in_editor_(base.hide_in_editor_ || overlay.hide_in_editor_)
239  , hide_if_impassable_(base.hide_if_impassable_ || overlay.hide_if_impassable_)
240 {
241  if(description_.empty()) {
242  description_ = base.description();
243  }
244 
245  if(overlay.height_adjust_set_) {
246  height_adjust_set_ = true;
247  height_adjust_ = overlay.height_adjust_;
248  }
249 
250  if(overlay.submerge_set_) {
251  submerge_set_ = true;
252  submerge_ = overlay.submerge_;
253  }
254 
258 
260  union_type_.insert( union_type_.end(), def_type_.begin(), def_type_.end() );
261  union_type_.insert( union_type_.end(), vision_type_.begin(), vision_type_.end() );
262 
263  // remove + and -
266 
267  // remove doubles
268  std::sort(union_type_.begin(),union_type_.end());
269  union_type_.erase(std::unique(union_type_.begin(), union_type_.end()), union_type_.end());
270 
271  //mouse over message are only shown on villages
272  if(base.village_) {
277  }
278  else if (overlay.village_) {
279  income_description_ = overlay.income_description_;
280  income_description_ally_ = overlay.income_description_ally_;
281  income_description_enemy_ = overlay.income_description_enemy_;
282  income_description_own_ = overlay.income_description_own_;
283  }
284 }
285 
287  if(overlay_ && has_default_base()) {
289  }
290  return number_;
291 }
292 
293 bool terrain_type::operator==(const terrain_type& other) const {
294  return minimap_image_ == other.minimap_image_
296  && editor_image_ == other.editor_image_
297  && id_ == other.id_
298  && name_.base_str() == other.name_.base_str()
300  && number_ == other.number_
301  && mvt_type_ == other.mvt_type_
302  && vision_type_ == other.vision_type_
303  && def_type_ == other.def_type_
304  && union_type_ == other.union_type_
305  && height_adjust_ == other.height_adjust_
307  && submerge_ == other.submerge_
308  && submerge_set_ == other.submerge_set_
310  && max_light_ == other.max_light_
311  && min_light_ == other.min_light_
312  && heals_ == other.heals_
313  && village_ == other.village_
314  && castle_ == other.castle_
315  && keep_ == other.keep_
316  && combined_ == other.combined_
317  && overlay_ == other.overlay_
319  && hide_in_editor_ == other.hide_in_editor_
320  && hide_help_ == other.hide_help_;
321 }
322 
324 {
325  // Insert second vector into first when the terrain _ref^base is encountered
326 
327  bool revert = (first.front() == t_translation::MINUS ? true : false);
329 
330  for(i = first.begin(); i != first.end(); ++i) {
331  if(*i == t_translation::PLUS) {
332  revert = false;
333  continue;
334  } else if(*i == t_translation::MINUS) {
335  revert = true;
336  continue;
337  }
338 
339  // This only works for a subset of the possible cases, and doesn't work for
340  // worst(best(a,b),c,d) terrain. Part of the reason that it doesn't work is that
341  // movetype.cpp starts with a default value of either UNREACHABLE or zero, which
342  // when inverted would drown out the values in best(a,b). Another part of the reason
343  // is that the insertion of a plus or minus before the base terrain is commented out
344  // in this function.
345 
346  if(*i == t_translation::BASE) {
347  t_translation::ter_list::iterator insert_it = first.erase(i);
348  //if we are in reverse mode, insert PLUS before and MINUS after the base list
349  //so calculation of base aliases will work normal
350  if(revert) {
351 // insert_it = first.insert(insert_it, t_translation::PLUS);
352 // insert_it++;
353  insert_it = first.insert(insert_it, t_translation::MINUS);
354  }
355  else {
356  //else insert PLUS after the base aliases to restore previous "reverse state"
357  insert_it = first.insert(insert_it, t_translation::PLUS);
358  }
359 
360  first.insert(insert_it, second.begin(), second.end());
361 
362  break;
363  }
364  }
365 
366 }
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:180
bool castle_
Definition: terrain.hpp:258
bool submerge_set_
Definition: terrain.hpp:244
int light_modification_
Definition: terrain.hpp:246
std::string id_
Definition: terrain.hpp:224
t_string income_description_
Definition: terrain.hpp:251
bool overlay_
Definition: terrain.hpp:260
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:223
t_translation::terrain_code editor_default_base_
Definition: terrain.hpp:261
t_translation::terrain_code terrain_with_default_base() const
Return the overlay part of this terrain, on the default_base().
Definition: terrain.cpp:286
int height_adjust_
Definition: terrain.hpp:240
t_translation::ter_list mvt_type_
Definition: terrain.hpp:235
t_string editor_name_
Definition: terrain.hpp:226
std::string minimap_image_overlay_
Definition: terrain.hpp:217
int max_light_
Definition: terrain.hpp:247
t_translation::terrain_code number_
Definition: terrain.hpp:234
t_string income_description_ally_
Definition: terrain.hpp:252
t_translation::ter_list union_type_
Definition: terrain.hpp:238
bool combined_
Definition: terrain.hpp:260
bool hide_help_
Definition: terrain.hpp:262
t_translation::ter_list def_type_
Definition: terrain.hpp:237
t_string description_
Definition: terrain.hpp:227
const t_string & description() const
Definition: terrain.hpp:50
std::string minimap_image_
The image used in the minimap.
Definition: terrain.hpp:216
terrain_type()
Creates an instance for which is_nonnull() returns false.
Definition: terrain.cpp:34
double submerge_
Definition: terrain.hpp:243
t_string income_description_own_
Definition: terrain.hpp:254
t_translation::ter_list vision_type_
Definition: terrain.hpp:236
int min_light_
Definition: terrain.hpp:248
bool village_
Definition: terrain.hpp:258
bool hide_in_editor_
Definition: terrain.hpp:262
t_string name_
Definition: terrain.hpp:225
bool height_adjust_set_
Definition: terrain.hpp:241
t_string income_description_enemy_
Definition: terrain.hpp:253
bool operator==(const terrain_type &other) const
Returns true if most of the data matches.
Definition: terrain.cpp:293
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
const config * cfg
std::size_t i
Definition: function.cpp:1032
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.
const terrain_code VOID_TERRAIN
VOID_TERRAIN is used for shrouded hexes.
const terrain_code MINUS
const ter_layer NO_LAYER
Definition: translation.hpp:40
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.
std::string write_terrain_code(const terrain_code &tcode)
Writes a single terrain code to a string.
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: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:323
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.