The Battle for Wesnoth  1.19.18+dev
map_context.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
3  by Tomasz Sniatowski <kailoran@gmail.com>
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 #define GETTEXT_DOMAIN "wesnoth-editor"
17 
19 
20 #include "display.hpp"
21 #include "editor/action/action.hpp"
22 #include "filesystem.hpp"
23 #include "formula/string_utils.hpp"
24 #include "gettext.hpp"
25 #include "gui/dialogs/message.hpp"
27 #include "map/label.hpp"
30 #include "serialization/parser.hpp"
32 #include "team.hpp"
33 #include "units/unit.hpp"
34 
35 #include <boost/regex.hpp>
36 
37 namespace editor
38 {
40  : side(t.side())
41  , id(t.team_name())
42  , name(t.user_team_name())
43  , recruit_list(utils::join(t.recruits(), ","))
44  , gold(t.gold())
45  , income(t.base_income())
46  , village_income(t.village_gold())
48  , fog(t.uses_fog())
49  , shroud(t.uses_shroud())
50  , share_vision(t.share_vision())
52  , no_leader(t.no_leader())
53  , hidden(t.hidden())
54 {
55 }
56 
57 const std::size_t map_context::max_action_stack_size_ = 100;
58 
59 namespace {
60  static const int editor_team_default_gold = 100;
61 }
62 
63 map_context::map_context(const editor_map& map, bool pure_map, const std::string& addon_id)
64  : filename_()
65  , map_data_key_()
66  , embedded_(false)
67  , pure_map_(pure_map)
68  , map_(map)
69  , undo_stack_()
70  , redo_stack_()
71  , actions_since_save_(0)
72  , needs_reload_(false)
73  , needs_terrain_rebuild_(false)
74  , needs_labels_reset_(false)
75  , everything_changed_(false)
76  , changed_locations_()
77  , starting_position_label_locs_()
78  , addon_id_(addon_id)
79  , previous_cfg_()
80  , scenario_id_()
81  , scenario_name_()
82  , scenario_description_()
83  , xp_mod_()
84  , victory_defeated_(true)
85  , random_time_(false)
86  , active_area_(-1)
87  , labels_(nullptr)
88  , units_()
89  , teams_()
90  , tod_manager_(new tod_manager)
91  , mp_settings_()
92  , game_classification_()
93  , music_tracks_()
94  , last_map_generator_(nullptr)
95 {
96 }
97 
98 static std::string get_map_location(const std::string& file_contents, const std::string& attr)
99 {
100  std::size_t attr_name_start = file_contents.find(attr);
101  if(attr_name_start == std::string::npos) return "";
102 
103  std::size_t attr_value_start = file_contents.find("=", attr_name_start);
104  std::size_t line_end = file_contents.find("\n", attr_name_start);
105  if(line_end < attr_value_start) return "";
106 
107  attr_value_start++;
108  std::string attr_value = file_contents.substr(attr_value_start, line_end - attr_value_start);
109  std::string_view v2 = attr_value;
110  utils::trim(v2);
111 
112  return std::string(v2);
113 }
114 
115 map_context::map_context(const std::string& filename, const std::string& addon_id)
117  , map_data_key_()
118  , embedded_(false)
119  , pure_map_(false)
120  , map_()
121  , undo_stack_()
122  , redo_stack_()
123  , actions_since_save_(0)
124  , needs_reload_(false)
125  , needs_terrain_rebuild_(false)
126  , needs_labels_reset_(false)
127  , everything_changed_(false)
128  , changed_locations_()
129  , starting_position_label_locs_()
130  , addon_id_(addon_id)
131  , previous_cfg_()
132  , scenario_id_()
133  , scenario_name_()
134  , scenario_description_()
135  , xp_mod_()
136  , victory_defeated_(true)
137  , random_time_(false)
138  , active_area_(-1)
139  , labels_(nullptr)
140  , units_()
141  , teams_()
142  , tod_manager_(new tod_manager)
143  , mp_settings_()
144  , game_classification_()
145  , music_tracks_()
146  , last_map_generator_(nullptr)
147 {
148  /*
149  * Overview of situations possibly found in the file:
150  *
151  * embedded_ - the map data is directly in the scenario file
152  * pure_map_ - the map data is in its own separate file (map_file, map_data+macro inclusion) or this is a .map file
153  *
154  * an editor-generated file uses neither of these and is its own thing - it's not embedded (since the editor now saves using map_file) and it's not a pure map since there's also scenario data involved
155  *
156  * 0. Not a scenario or map file.
157  * 0.1 File not found
158  * 0.2 Map file empty
159  * 0.3 Not a .map or .cfg file
160  * 1. It's a .map file.
161  * * embedded_ = false
162  * * pure_map_ = true
163  * 2. A scenario embedding the map
164  * * embedded_ = true
165  * * pure_map_ = true
166  * The scenario-test.cfg for example.
167  * The map is written back to the file.
168  * 3. The map file is referenced by map_data={MACRO_ARGUEMENT}.
169  * * embedded_ = false
170  * * pure_map_ = true
171  * 4. The file contains an editor generated scenario file.
172  * * embedded_ = false
173  * * pure_map_ = false
174  * 5. The file is using map_file.
175  * 5.1 The file doesn't contain a macro and so can be loaded by the editor as a scenario
176  * * embedded_ = false
177  * * pure_map_ = false
178  * 5.2 The file contains a macro and so can't be loaded by the editor as a scenario
179  * * embedded_ = false
180  * * pure_map_ = true
181  */
182 
183  log_scope2(log_editor, "Loading file " + filename);
184 
185  // 0.1 File not found
187  throw editor_map_load_exception(filename, _("File not found"));
188  }
189 
190  std::string file_string = filesystem::read_file(filename);
191 
192  // 0.2 Map file empty
193  if(file_string.empty()) {
194  std::string message = _("Empty file");
195  throw editor_map_load_exception(filename, message);
196  }
197 
198  // 0.3 Not a .map or .cfg file
202  {
203  std::string message = _("File does not have .map, .cfg, or .mask extension");
204  throw editor_map_load_exception(filename, message);
205  }
206 
207  // 1.0 Pure map data
210  LOG_ED << "Loading map or mask file";
211  map_ = editor_map::from_string(file_string); // throws on error
212  pure_map_ = true;
213 
215  } else {
216  // 4.0 old-style editor generated scenario which lacks a top-level tag
217  if(file_string.find("[multiplayer]") == std::string::npos &&
218  file_string.find("[scenario]") == std::string::npos &&
219  file_string.find("[test]") == std::string::npos) {
220  LOG_ED << "Loading generated scenario file";
221  try {
222  load_scenario();
223  } catch(const std::exception& e) {
224  throw editor_map_load_exception("load_scenario: old-style scenario", e.what());
225  }
227  } else {
228  std::string map_data_loc = get_map_location(file_string, "map_data");
229  std::string map_file_loc = get_map_location(file_string, "map_file");
230 
231  if(!map_data_loc.empty()) {
232  if(map_data_loc.find("\"{") == std::string::npos) {
233  // 2.0 Embedded pure map
234  LOG_ED << "Loading embedded map file";
235  embedded_ = true;
236  pure_map_ = true;
237  std::size_t start = file_string.find(map_data_loc)+1;
238  std::size_t length = file_string.find("\"", start)-start;
239  std::string map_data = file_string.substr(start, length);
240  map_ = editor_map::from_string(map_data);
242  } else {
243  // 3.0 Macro referenced pure map
244  const std::string& macro_argument = map_data_loc.substr(2, map_data_loc.size()-4);
245  LOG_ED << "Map looks like a scenario, trying {" << macro_argument << "}";
246 
248 
249  if(!new_filename) {
250  std::string message = _("The map file looks like a scenario, but the map_data value does not point to an existing file")
251  + std::string("\n") + macro_argument;
252  throw editor_map_load_exception(filename, message);
253  }
254 
255  LOG_ED << "New filename is: " << new_filename.value();
256 
257  filename_ = new_filename.value();
258  file_string = filesystem::read_file(filename_);
259  map_ = editor_map::from_string(file_string);
260  pure_map_ = true;
261 
263  }
264  } else if(!map_file_loc.empty()) {
265  // 5.0 The file is using map_file.
266  try {
267  // 5.1 The file can be loaded by the editor as a scenario
268  if(file_string.find("<<") != std::string::npos) {
269  throw editor_map_load_exception(filename, _("Found the characters ‘<<’ indicating inline lua is present — aborting"));
270  }
271  load_scenario();
272  } catch(const std::exception&) {
273  // 5.2 The file can't be loaded by the editor as a scenario, so try to just load the map
274  gui2::show_message(_("Error"), _("Failed to load the scenario, attempting to load only the map."), gui2::dialogs::message::auto_close);
275 
276  // NOTE: this means that loading the map file from a scenario where the maps are in nested directories under maps/ will not work
277  // this is done to address mainline scenarios referencing their maps as "multiplayer/maps/<map_file>.map"
278  // otherwise this results in the "multiplayer/maps/" part getting duplicated in the path and then not being found
279  std::string new_filename = filesystem::get_current_editor_dir(addon_id_) + "/maps/" + filesystem::base_name(map_file_loc);
280  if(!filesystem::file_exists(new_filename)) {
281  std::string message = _("The map file looks like a scenario, but the map_file value does not point to an existing file")
282  + std::string("\n") + new_filename;
283  throw editor_map_load_exception(filename, message);
284  }
285 
286  LOG_ED << "New filename is: " << new_filename;
287 
288  filename_ = new_filename;
289  file_string = filesystem::read_file(filename_);
290  map_ = editor_map::from_string(file_string);
291  pure_map_ = true;
292  }
293 
295  } else {
296  throw editor_map_load_exception(filename, _("Unable to parse file to find map data"));
297  }
298  }
299  }
300 }
301 
303 {
304  undo_stack_.clear();
305  redo_stack_.clear();
306 }
307 
309 {
310  teams_.emplace_back();
311 
312  config cfg;
313  cfg["side"] = teams_.size(); // side is 1-indexed, so we can just use size()
314  cfg["hidden"] = false;
315  cfg["gold"] = editor_team_default_gold;
316 
317  teams_.back().build(cfg, map());
318 
320 }
321 
323 {
324  assert(teams_.size() >= static_cast<unsigned int>(info.side));
325 
326  team& t = teams_[info.side - 1];
327  t.change_team(info.id, info.name);
328  t.set_recruits(utils::split_set(info.recruit_list, ','));
329  t.have_leader(!info.no_leader);
330  t.change_controller(info.controller);
331  t.set_gold(info.gold);
332  t.set_base_income(info.income);
333  t.set_hidden(info.hidden);
334  t.set_fog(info.fog);
335  t.set_shroud(info.shroud);
336  t.set_share_vision(info.share_vision);
337  t.set_village_gold(info.village_income);
338  t.set_village_support(info.village_support);
339 
341 }
342 
343 void map_context::set_scenario_setup(const std::string& id,
344  const std::string& name,
345  const std::string& description,
346  int turns,
347  int xp_mod,
348  bool victory_defeated,
349  bool random_time)
350 {
351  scenario_id_ = id;
352  scenario_name_ = name;
353  scenario_description_ = description;
354  random_time_ = random_time;
356  tod_manager_->set_number_of_turns(turns);
357  xp_mod_ = xp_mod;
359 }
360 
362 {
363  tod_manager_->set_current_time(time);
364  if(!pure_map_) {
366  }
367 }
368 
370 {
371  tod_manager_->remove_time_area(index);
372  active_area_--;
374 }
375 
376 void map_context::replace_schedule(const std::vector<time_of_day>& schedule)
377 {
378  tod_manager_->replace_schedule(schedule);
379  if(!pure_map_) {
381  }
382 }
383 
384 void map_context::replace_local_schedule(const std::vector<time_of_day>& schedule)
385 {
386  tod_manager_->replace_local_schedule(schedule, active_area_);
387  if(!pure_map_) {
389  }
390 }
391 
393 {
394  config cfg;
395  config& multiplayer = cfg.add_child("multiplayer");
396  multiplayer.append_attributes(old_scenario);
397  std::string map_data = multiplayer["map_data"];
398  std::string separate_map_file = filesystem::get_current_editor_dir(addon_id_) + "/maps/" + filesystem::base_name(filename_, true) + filesystem::map_extension;
399 
400  // check that there's embedded map data, since that's how the editor used to save scenarios
401  if(!map_data.empty()) {
402  // check if a .map file already exists as a separate standalone .map in the editor folders or if a .map file already exists in the add-on
403  if(filesystem::file_exists(separate_map_file)) {
405  }
406  multiplayer["id"] = filesystem::base_name(separate_map_file, true);
407 
408  filesystem::write_file(separate_map_file, map_data);
409  multiplayer.remove_attribute("map_data");
410  multiplayer["map_file"] = filesystem::base_name(separate_map_file);
411  } else {
412  ERR_ED << "Cannot convert " << filename_ << " due to missing map_data attribute.";
413  throw editor_map_load_exception("load_scenario: no embedded map_data attribute found in old-style scenario", filename_);
414  }
415 
416  config& event = multiplayer.add_child("event");
417  event["name"] = "prestart";
418  event["id"] = "editor_event-prestart";
419 
420  // for all children that aren't [side] or [time], move them to an event
421  // for [side]:
422  // keep all attributes in [side]
423  // also keep any [village]s in [side]
424  // move all other children to the start [event]
425  // if [unit], set the unit's side
426  // for [time]:
427  // keep under [multiplayer]
428  for(const auto [child_key, child_cfg]: old_scenario.all_children_view()) {
429  if(child_key != "side" && child_key != "time") {
430  config& c = event.add_child(child_key);
431  c.append_attributes(child_cfg);
432  c.append_children(child_cfg);
433  } else if(child_key == "side") {
434  config& c = multiplayer.add_child("side");
435  c.append_attributes(child_cfg);
436  for(const auto [side_key, side_cfg] : child_cfg.all_children_view()) {
437  if(side_key == "village") {
438  config& c1 = c.add_child("village");
439  c1.append_attributes(side_cfg);
440  } else {
441  config& c1 = event.add_child(side_key);
442  c1.append_attributes(side_cfg);
443  if(side_key == "unit") {
444  c1["side"] = child_cfg["side"];
445  }
446  }
447  }
448  } else if(child_key == "time") {
449  config& c = multiplayer.add_child("time");
450  c.append_attributes(child_cfg);
451  }
452  }
453 
454  return cfg;
455 }
456 
458 {
460 
461  config scenario;
462  if(scen.has_child("scenario")) {
463  scenario = scen.mandatory_child("scenario");
464  } else if(scen.has_child("multiplayer")) {
465  scenario = scen.mandatory_child("multiplayer");
466  } else if(scen.has_child("test")) {
467  scenario = scen.mandatory_child("test");
468  } else {
469  ERR_ED << "Found no [scenario], [multiplayer], or [test] tag in " << filename_ << ", assuming old-style editor scenario and defaulting to [multiplayer]";
470  scen = convert_scenario(scen);
471  scenario = scen.mandatory_child("multiplayer");
472  }
473 
474  scenario_id_ = scenario["id"].str();
475  scenario_name_ = scenario["name"].str();
476  scenario_description_ = scenario["description"].str();
477 
478  if(const config::attribute_value* experience_modifier = scenario.get("experience_modifier")) {
479  xp_mod_ = experience_modifier->to_int();
480  }
481  victory_defeated_ = scenario["victory_when_enemies_defeated"].to_bool(true);
482  random_time_ = scenario["random_start_time"].to_bool(false);
483 
484  if(!scenario["map_data"].str().empty()) {
485  map_ = editor_map::from_string(scenario["map_data"].str()); // throws on error
486  } else if(!scenario["map_file"].str().empty()) {
488  } else {
489  throw editor_map_load_exception("load_scenario: no map_file or map_data attribute found", filename_);
490  }
491 
492  for(config& side : scenario.child_range("side")) {
493  teams_.emplace_back();
494  teams_.back().build(side, map_);
495  if(!side["recruit"].str().empty()) {
496  teams_.back().set_recruits(utils::split_set(side["recruit"].str(), ','));
497  }
498  }
499 
500  tod_manager_.reset(new tod_manager(scenario));
501 
502  auto event = scenario.find_child("event", "id", "editor_event-start");
503  if(!event) {
504  event = scenario.find_child("event", "id", "editor_event-prestart");
505  }
506  if(event) {
507  config& evt = event.value();
508 
509  labels_.read(evt);
510 
511  for(const config& time_area : evt.child_range("time_area")) {
512  tod_manager_->add_time_area(map_, time_area);
513  }
514 
515  for(const config& item : evt.child_range("item")) {
516  const map_location loc(item);
517  overlays_[loc].push_back(overlay(item));
518  }
519 
520  for(const config& music : evt.child_range("music")) {
521  music_tracks_.emplace_back(sound::music_track::create(music));
522  }
523 
524  for(config& a_unit : evt.child_range("unit")) {
525  units_.insert(unit::create(a_unit, true));
526  }
527  }
528 
529  previous_cfg_ = scen;
530 }
531 
533 {
534  return map_.set_selection(tod_manager_->get_area_by_index(index));
535 }
536 
537 bool map_context::playlist_contains(const std::shared_ptr<sound::music_track>& track) const
538 {
539  return utils::contains(music_tracks_, track);
540 }
541 
542 void map_context::toggle_track(const std::shared_ptr<sound::music_track>& track)
543 {
544  if(playlist_contains(track)) {
545  music_tracks_.remove(track);
546  } else {
547  music_tracks_.push_back(track);
548  }
549 }
550 
551 void map_context::draw_terrain(const t_translation::terrain_code& terrain, const map_location& loc, bool one_layer_only)
552 {
553  t_translation::terrain_code full_terrain = one_layer_only
554  ? terrain
556 
557  draw_terrain_actual(full_terrain, loc, one_layer_only);
558 }
559 
561  const t_translation::terrain_code& terrain, const map_location& loc, bool one_layer_only)
562 {
564  // requests for painting off the map are ignored in set_terrain anyway,
565  // but ideally we should not have any
566  LOG_ED << "Attempted to draw terrain off the map (" << loc << ")";
567  return;
568  }
569 
571 
572  if(terrain != old_terrain) {
573  if(terrain.base == t_translation::NO_LAYER) {
575  } else if(one_layer_only) {
577  } else {
578  map_.set_terrain(loc, terrain);
579  }
580 
582  }
583 }
584 
586  const t_translation::terrain_code& terrain, const std::set<map_location>& locs, bool one_layer_only)
587 {
588  t_translation::terrain_code full_terrain = one_layer_only
589  ? terrain
591 
592  for(const map_location& loc : locs) {
593  draw_terrain_actual(full_terrain, loc, one_layer_only);
594  }
595 }
596 
598 {
599  everything_changed_ = false;
600  changed_locations_.clear();
601 }
602 
604 {
605  if(!everything_changed()) {
606  changed_locations_.insert(loc);
607  }
608 }
609 
610 void map_context::add_changed_location(const std::set<map_location>& locs)
611 {
612  if(!everything_changed()) {
613  changed_locations_.insert(locs.begin(), locs.end());
614  }
615 }
616 
618 {
619  everything_changed_ = true;
620 }
621 
623 {
624  return everything_changed_;
625 }
626 
628 {
629  disp.labels().clear_all();
631 }
632 
634 {
635  std::set<map_location> new_label_locs = map_.set_starting_position_labels(disp);
636  starting_position_label_locs_.insert(new_label_locs.begin(), new_label_locs.end());
637 }
638 
640 {
643  set_needs_labels_reset(false);
644 }
645 
647 {
648  config scen;
649 
650  // Textdomain
651  std::string current_textdomain = "wesnoth-"+addon_id_;
652 
653  // the state of the previous scenario cfg
654  // if it exists, alter specific parts of it (sides, times, and editor events) rather than replacing it entirely
655  if(previous_cfg_) {
656  scen = *previous_cfg_;
657  }
658 
659  // if this has [multiplayer], use [multiplayer]
660  // else if this has [scenario], use [scenario]
661  // else if this has [test], use [test]
662  // else if none, add a [multiplayer]
663  config& scenario = scen.has_child("multiplayer")
664  ? scen.mandatory_child("multiplayer")
665  : scen.has_child("scenario")
666  ? scen.mandatory_child("scenario")
667  : scen.has_child("test")
668  ? scen.mandatory_child("test")
669  : scen.add_child("multiplayer");
670 
671  scenario.remove_children("side");
672  scenario.remove_children("event", [](const config& cfg) {
673  return cfg["id"].str() == "editor_event-start" || cfg["id"].str() == "editor_event-prestart";
674  });
675  scenario.remove_children("time");
676 
677  scenario["id"] = scenario_id_;
678  scenario["name"] = t_string(scenario_name_, current_textdomain);
679  scenario["description"] = t_string(scenario_description_, current_textdomain);
680 
681  if(xp_mod_) {
682  scenario["experience_modifier"] = *xp_mod_;
683  }
684  if(victory_defeated_) {
685  scenario["victory_when_enemies_defeated"] = *victory_defeated_;
686  }
687  scenario["random_start_time"] = random_time_;
688 
689  // write out the map data
690  scenario["map_file"] = scenario_id_ + filesystem::map_extension;
692 
693  // find or add the editor's start event
694  config& event = scenario.add_child("event");
695  event["name"] = "prestart";
696  event["id"] = "editor_event-prestart";
697  event["priority"] = 1000;
698 
699  // write out all the scenario data below
700 
701  // [time]s and [time_area]s
702  config times = tod_manager_->to_config(current_textdomain);
703 
704  // TODO: random_start_time is written separately above. Should we use the value from the ToD manager?
705  times.remove_attributes("turn_at", "it_is_a_new_turn", "random_start_time");
706 
707  if(times["turns"].to_int() == -1) {
708  times.remove_attribute("turns");
709  }
710 
711  if(times["current_time"].to_int() == 0) {
712  times.remove_attribute("current_time");
713  }
714 
715  scenario.merge_attributes(times);
716  scenario.append_children_by_move(times, "time");
717 
718  // put the [time_area]s into the event to keep as much editor-specific stuff separated in its own event as possible
719  event.append_children_by_move(times, "time_area");
720 
721  // [label]s
722  labels_.write(event);
723 
724  // [item]s
725  for(const auto& overlay_pair : overlays_) {
726  for(const overlay& o : overlay_pair.second) {
727  config& item = event.add_child("item");
728 
729  // Write x,y location
730  overlay_pair.first.write(item);
731 
732  // These should always have a value
733  item["image"] = o.image;
734  item["visible_in_fog"] = o.visible_in_fog;
735 
736  // Optional keys
737  item["id"].write_if_not_empty(o.id);
738  item["name"].write_if_not_empty(t_string(o.name, current_textdomain));
739  item["team_name"].write_if_not_empty(o.team_name);
740  item["halo"].write_if_not_empty(o.halo);
741  if(o.submerge) {
742  item["submerge"] = o.submerge;
743  }
744  }
745  }
746 
747  // [music]s
748  for(const auto& track : music_tracks_) {
749  track->write(event, true);
750  }
751 
752  // [unit]s
754  preprocess_file(game_config::path + "/data/core/macros/traits.cfg", traits_map);
755 
756  for(const auto& unit : units_) {
757  config& u = event.add_child("unit");
758 
759  unit.get_location().write(u);
760 
761  u["side"] = unit.side();
762  u["type"] = unit.type_id();
763  u["name"].write_if_not_empty(t_string(unit.name(), current_textdomain));
764  u["facing"] = map_location::write_direction(unit.facing());
765 
766  if(!boost::regex_match(unit.id(), boost::regex(".*-[0-9]+"))) {
767  u["id"] = unit.id();
768  }
769 
770  if(unit.can_recruit()) {
771  u["canrecruit"] = unit.can_recruit();
772  }
773 
774  if(unit.unrenamable()) {
775  u["unrenamable"] = unit.unrenamable();
776  }
777 
778  config& mods = u.add_child("modifications");
779  if(unit.loyal()) {
780  mods.append(io::read(*preprocess_string("{TRAIT_LOYAL}", "wesnoth-help", traits_map)));
781  }
782  //TODO this entire block could also be replaced by unit.write(u, true)
783  //however, the resultant config is massive and contains many attributes we don't need.
784  //need to find a middle ground here.
785  }
786 
787  // [side]s
788  for(const auto& team : teams_) {
789  config& side = scenario.add_child("side");
790 
791  side["side"] = scenario.child_count("side");
792  side["hidden"] = team.hidden();
793 
794  side["controller"] = side_controller::get_string(team.controller());
795  side["no_leader"] = team.no_leader();
796 
797  side["team_name"] = team.team_name();
798  side["user_team_name"].write_if_not_empty(t_string(team.user_team_name(), current_textdomain));
799  if(team.recruits().size() > 0) {
800  side["recruit"] = utils::join(team.recruits(), ",");
801  side["faction"] = "Custom";
802  }
803 
804  side["fog"] = team.uses_fog();
805  side["shroud"] = team.uses_shroud();
806  side["share_vision"] = team_shared_vision::get_string(team.share_vision());
807 
808  side["gold"] = team.gold();
809  side["income"] = team.raw_income();
810 
811  for(const map_location& village : team.villages()) {
812  village.write(side.add_child("village"));
813  }
814  }
815 
816  previous_cfg_ = scen;
817  return scen;
818 }
819 
820 void map_context::save_schedule(const std::string& schedule_id, const std::string& schedule_name)
821 {
822  // Textdomain
823  std::string current_textdomain = "wesnoth-"+addon_id_;
824 
825  // Path to schedule.cfg
826  std::string schedule_path = filesystem::get_current_editor_dir(addon_id_) + "/utils/schedule.cfg";
827 
828  // Create schedule config
829  config schedule;
830  try {
831  if (filesystem::file_exists(schedule_path)) {
832  /* If exists, read the schedule.cfg
833  * and insert [editor_times] block at correct place */
835  editor_map.try_emplace("EDITOR");
836  schedule = io::read(*preprocess_file(schedule_path, editor_map));
837  }
838  } catch(const filesystem::io_exception& e) {
839  utils::string_map symbols;
840  symbols["msg"] = e.what();
841  const std::string msg = VGETTEXT("Could not save time schedule: $msg", symbols);
843  }
844 
845  config& editor_times = schedule.add_child("editor_times");
846 
847  editor_times["id"] = schedule_id;
848  editor_times["name"] = t_string(schedule_name, current_textdomain);
849  config times = tod_manager_->to_config(current_textdomain);
850  for(const config& time : times.child_range("time")) {
851  config& t = editor_times.add_child("time");
852  t.append(time);
853  }
854 
855  // Write to file
856  try {
857  std::stringstream wml_stream;
858 
859  wml_stream
860  << "#textdomain " << current_textdomain << "\n"
861  << "#\n"
862  << "# This file was generated using the scenario editor.\n"
863  << "#\n"
864  << "#ifdef EDITOR\n";
865 
866  {
867  config_writer out(wml_stream, false);
868  out.write(schedule);
869  }
870 
871  wml_stream << "#endif";
872 
873  if(!wml_stream.str().empty()) {
874  filesystem::write_file(schedule_path, wml_stream.str());
875  gui2::show_transient_message("", _("Time schedule saved."));
876  }
877 
878  } catch(const filesystem::io_exception& e) {
879  utils::string_map symbols;
880  symbols["msg"] = e.what();
881  const std::string msg = VGETTEXT("Could not save time schedule: $msg", symbols);
883  }
884 }
885 
887 {
888  assert(!is_embedded());
889 
890  if(scenario_id_.empty()) {
892  }
893 
894  if(scenario_name_.empty()) {
896  }
897 
898  try {
899  std::stringstream wml_stream;
900  wml_stream
901  << "# This file was generated using the scenario editor.\n"
902  << "#\n"
903  << "# If you edit this file by hand, then do not use macros.\n"
904  << "# The editor doesn't support macros, and so using them will result in only being able to edit the map.\n"
905  << "# Additionally, the contents of all [side] and [time] tags as well as any events that have an id starting with 'editor_event-' are replaced entirely.\n"
906  << "# Any manual changes made to those will be lost.\n"
907  << "\n";
908  {
909  config_writer out(wml_stream, false);
910  out.write(to_config());
911  }
912 
913  if(!wml_stream.str().empty()) {
914  filesystem::write_file(get_filename(), wml_stream.str());
915  }
916 
917  clear_modified();
918  } catch(const filesystem::io_exception& e) {
919  utils::string_map symbols;
920  symbols["msg"] = e.what();
921  const std::string msg = VGETTEXT("Could not save the scenario: $msg", symbols);
922 
924  }
925 
926  // After saving the map as a scenario, it's no longer a pure map.
927  pure_map_ = false;
928 }
929 
931 {
932  std::string map_data = map_.write();
933 
934  try {
935  if(!is_embedded()) {
937  } else {
938  std::string map_string = filesystem::read_file(get_filename());
939 
940  boost::regex rexpression_map_data(R"((.*map_data\s*=\s*")(.+?)(".*))");
941  boost::smatch matched_map_data;
942 
943  if(boost::regex_search(map_string, matched_map_data, rexpression_map_data,
944  boost::regex_constants::match_not_dot_null)) {
945  std::stringstream ss;
946  ss << matched_map_data[1];
947  ss << map_data;
948  ss << matched_map_data[3];
949 
951  } else {
952  throw editor_map_save_exception(_("Could not save into scenario"));
953  }
954  }
955 
957 
958  clear_modified();
959  } catch(const filesystem::io_exception& e) {
960  utils::string_map symbols;
961  symbols["msg"] = e.what();
962  const std::string msg = VGETTEXT("Could not save the map: $msg", symbols);
963 
965  }
966 }
967 
969 {
970  if(map_.h() != map.h() || map_.w() != map.w()) {
972  } else {
974  }
975 
976  map_ = map;
977 }
978 
980 {
981  LOG_ED << "Performing action " << action.get_id() << ": " << action.get_name() << ", actions count is "
982  << action.get_instance_count();
983  auto undo = action.perform(*this);
984  if(actions_since_save_ < 0) {
985  // set to a value that will make it impossible to get to zero, as at this point
986  // it is no longer possible to get back the original map state using undo/redo
987  actions_since_save_ = 1 + undo_stack_.size();
988  }
989 
991 
992  undo_stack_.emplace_back(std::move(undo));
993 
995 
996  redo_stack_.clear();
997 }
998 
1000 {
1001  LOG_ED << "Performing (partial) action " << action.get_id() << ": " << action.get_name() << ", actions count is "
1002  << action.get_instance_count();
1003  if(!can_undo()) {
1004  throw editor_logic_exception("Empty undo stack in perform_partial_action()");
1005  }
1006 
1007  editor_action_chain* undo_chain = dynamic_cast<editor_action_chain*>(last_undo_action());
1008  if(undo_chain == nullptr) {
1009  throw editor_logic_exception("Last undo action not a chain in perform_partial_action()");
1010  }
1011 
1012  auto undo = action.perform(*this);
1013 
1014  // actions_since_save_ += action.action_count();
1015  undo_chain->prepend_action(std::move(undo));
1016 
1017  redo_stack_.clear();
1018 }
1019 
1021 {
1022  return actions_since_save_ != 0;
1023 }
1024 
1026 {
1027  actions_since_save_ = 0;
1028 }
1029 
1031 {
1033 }
1034 
1036 {
1037  return !undo_stack_.empty();
1038 }
1039 
1041 {
1042  return !redo_stack_.empty();
1043 }
1044 
1046 {
1047  return undo_stack_.empty() ? nullptr : undo_stack_.back().get();
1048 }
1049 
1051 {
1052  return redo_stack_.empty() ? nullptr : redo_stack_.back().get();
1053 }
1054 
1056 {
1057  return undo_stack_.empty() ? nullptr : undo_stack_.back().get();
1058 }
1059 
1061 {
1062  return redo_stack_.empty() ? nullptr : redo_stack_.back().get();
1063 }
1064 
1066 {
1067  LOG_ED << "undo() beg, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1068 
1069  if(can_undo()) {
1072  } else {
1073  WRN_ED << "undo() called with an empty undo stack";
1074  }
1075 
1076  LOG_ED << "undo() end, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1077 }
1078 
1080 {
1081  LOG_ED << "redo() beg, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1082 
1083  if(can_redo()) {
1086  } else {
1087  WRN_ED << "redo() called with an empty redo stack";
1088  }
1089 
1090  LOG_ED << "redo() end, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1091 }
1092 
1094 {
1095  // callers should check for these conditions
1096  if(!can_undo()) {
1097  throw editor_logic_exception("Empty undo stack in partial_undo()");
1098  }
1099 
1100  editor_action_chain* undo_chain = dynamic_cast<editor_action_chain*>(last_undo_action());
1101  if(undo_chain == nullptr) {
1102  throw editor_logic_exception("Last undo action not a chain in partial undo");
1103  }
1104 
1105  // a partial undo performs the first action form the current action's action_chain that would be normally performed
1106  // i.e. the *first* one.
1107  const auto first_action_in_chain = undo_chain->pop_first_action();
1108  if(undo_chain->empty()) {
1110  undo_stack_.pop_back();
1111  }
1112 
1113  redo_stack_.emplace_back(first_action_in_chain->perform(*this));
1114  // actions_since_save_ -= last_redo_action()->action_count();
1115 }
1116 
1118 {
1119  undo_stack_.clear();
1120  redo_stack_.clear();
1121 }
1122 
1124 {
1125  if(stack.size() > max_action_stack_size_) {
1126  stack.pop_front();
1127  }
1128 }
1129 
1131 {
1132  assert(!from.empty());
1133 
1134  std::unique_ptr<editor_action> action;
1135  action.swap(from.back());
1136 
1137  from.pop_back();
1138 
1139  auto reverse_action = action->perform(*this);
1140  to.emplace_back(std::move(reverse_action));
1141 
1142  trim_stack(to);
1143 }
1144 
1146 {
1147  return is_pure_map() ? _("New Map") : _("New Scenario");
1148 }
1149 
1150 } // end namespace editor
std::string filename_
Definition: action_wml.cpp:534
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
Variant for storing WML attributes.
Class for writing a config out to a file in pieces.
void write(const config &cfg)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
void remove_attribute(std::string_view key)
Definition: config.cpp:162
config & add_child(std::string_view key)
Definition: config.cpp:436
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:188
void remove_attributes(T... keys)
Definition: config.hpp:536
void append_children_by_move(config &cfg, std::string_view key)
Moves children with the given name from the given config to this one.
Definition: config.cpp:224
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
void merge_attributes(const config &)
Definition: config.cpp:722
child_itors child_range(std::string_view key)
Definition: config.cpp:268
const attribute_value * get(std::string_view key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:665
optional_config_impl< config > find_child(std::string_view key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:764
void append_attributes(const config &cfg)
Adds attributes from cfg.
Definition: config.cpp:174
std::size_t child_count(std::string_view key) const
Definition: config.cpp:292
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
void remove_children(std::string_view key, const std::function< bool(const config &)> &p={})
Removes all children with tag key for which p returns true.
Definition: config.cpp:634
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
map_labels & labels()
Definition: display.cpp:2426
Container action wrapping several actions into one.
Definition: action.hpp:88
std::unique_ptr< editor_action > pop_first_action()
Remove the first added action and return it, transferring ownership to the caller.
Definition: action.cpp:142
void prepend_action(std::unique_ptr< editor_action > a)
Add an action at the beginning of the chain.
Definition: action.cpp:121
Base class for all editor actions.
Definition: action_base.hpp:42
int get_id() const
Debugging aid.
Definition: action_base.hpp:93
virtual const std::string & get_name() const
Definition: action_base.hpp:75
virtual std::unique_ptr< editor_action > perform(map_context &) const
Perform the action, returning an undo action that, when performed, shall reverse any effects of this ...
Definition: action.cpp:64
static int get_instance_count()
Debugging aid.
This class adds extra editor-specific functionality to a normal gamemap.
Definition: editor_map.hpp:70
bool set_selection(const std::set< map_location > &area)
Select the given area.
Definition: editor_map.cpp:168
std::set< map_location > set_starting_position_labels(display &disp)
Set labels for staring positions in the given display object.
Definition: editor_map.cpp:136
static editor_map from_string(std::string_view data)
Wrapper around editor_map(cfg, data) that catches possible exceptions and wraps them in a editor_map_...
Definition: editor_map.cpp:55
std::set< map_location > starting_position_label_locs_
Cache of set starting position labels.
std::string filename_
The actual filename of this map.
utils::optional< config > previous_cfg_
void perform_partial_action(const editor_action &action)
Performs a partial action, assumes that the top undo action has been modified to maintain coherent st...
bool playlist_contains(const std::shared_ptr< sound::music_track > &track) const
Checks whether the given track is part of current playlist.
bool modified() const
bool pure_map_
Whether the map context refers to a file containing only the pure map data.
action_stack redo_stack_
The redo stack.
std::unique_ptr< tod_manager > tod_manager_
bool embedded_
Whether the map context refers to a map embedded in a scenario file.
void set_needs_labels_reset(bool value=true)
Setter for the labels reset flag.
void set_needs_reload(bool value=true)
Setter for the reload flag.
std::string scenario_name_
void toggle_track(const std::shared_ptr< sound::music_track > &track)
Remove the given track from the current playlist if present, else appends it.
void clear_starting_position_labels(display &disp)
editor_map map_
The map object of this map_context.
void new_side()
Adds a new side to the map.
static const std::size_t max_action_stack_size_
Action stack (i.e.
action_stack undo_stack_
The undo stack.
editor_action * last_undo_action()
void redo()
Re-does a previously undid action, and puts it back in the undo stack.
std::set< map_location > changed_locations_
void set_starting_time(int time)
void draw_terrain(const t_translation::terrain_code &terrain, const map_location &loc, bool one_layer_only=false)
Draw a terrain on a single location on the map.
void set_map(const editor_map &map)
bool select_area(int index)
Select the nth tod area.
bool can_redo() const
void set_side_setup(editor_team_info &info)
void trim_stack(action_stack &stack)
Checks if an action stack reached its capacity and removes the front element if so.
bool can_undo() const
int actions_since_save_
Number of actions performed since the map was saved.
std::string scenario_description_
void save_scenario()
Saves the scenario under the current filename.
std::vector< team > teams_
void perform_action_between_stacks(action_stack &from, action_stack &to)
Perform an action at the back of one stack, and then move it to the back of the other stack.
void perform_action(const editor_action &action)
Performs an action (thus modifying the map).
void undo()
Un-does the last action, and puts it in the redo stack for a possible redo.
void replace_schedule(const std::vector< time_of_day > &schedule)
utils::optional< bool > victory_defeated_
void remove_area(int index)
void clear_undo_redo()
Clear the undo and redo stacks.
bool is_embedded() const
virtual ~map_context()
Map context destructor.
std::list< std::shared_ptr< sound::music_track > > music_tracks_
void draw_terrain_actual(const t_translation::terrain_code &terrain, const map_location &loc, bool one_layer_only=false)
Actual drawing function used by both overloaded variants of draw_terrain.
void clear_changed_locations()
map_context(const map_context &)=delete
utils::optional< int > xp_mod_
void add_to_recent_files()
Adds the map to the editor's recent files list.
std::string scenario_id_
void replace_local_schedule(const std::vector< time_of_day > &schedule)
Replace the [time]s of the currently active area.
void save_map()
Saves the map under the current filename.
void reset_starting_position_labels(display &disp)
editor_action * last_redo_action()
void partial_undo()
Un-does a single step from a undo action chain.
virtual const editor_map & map() const override
Const map accessor.
void set_needs_terrain_rebuild(bool value=true)
Setter for the terrain rebuild flag.
config convert_scenario(const config &old_scenario)
Convert an old-style editor scenario config to a config with a top level [multiplayer] tag.
bool everything_changed() const
const t_string get_default_context_name() const
const std::string & get_filename() const
void set_starting_position_labels(display &disp)
void clear_modified()
Clear the modified state.
overlay_map overlays_
bool is_pure_map() const
bool victory_defeated() const
std::string addon_id_
void set_scenario_setup(const std::string &id, const std::string &name, const std::string &description, int turns, int xp_mod, bool victory_defeated, bool random_time)
void add_changed_location(const map_location &loc)
void save_schedule(const std::string &schedule_id, const std::string &schedule_name)
Save custom time of day schedule in the utils directory.
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:271
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:358
std::string write() const
Definition: map.cpp:178
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:365
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
Definition: map.cpp:84
@ auto_close
Enables auto close.
Definition: message.hpp:71
void write(config &res) const
Definition: label.cpp:81
void clear_all()
Definition: label.cpp:241
void read(const config &cfg)
Definition: label.cpp:93
static prefs & get()
void add_recent_files_entry(const std::string &path)
Adds an entry to the recent files list.
static std::shared_ptr< music_track > create(const config &cfg)
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
bool uses_shroud() const
Definition: team.hpp:341
const std::string & team_name() const
Definition: team.hpp:320
bool no_leader() const
Definition: team.hpp:366
team_shared_vision::type share_vision() const
Definition: team.hpp:416
const std::set< map_location > & villages() const
Definition: team.hpp:175
int gold() const
Definition: team.hpp:180
int raw_income() const
Definition: team.hpp:186
side_controller::type controller() const
Definition: team.hpp:279
bool uses_fog() const
Definition: team.hpp:342
bool hidden() const
Definition: team.hpp:372
const std::set< std::string > & recruits() const
Definition: team.hpp:247
const t_string & user_team_name() const
Definition: team.hpp:321
t_translation::terrain_code terrain_with_default_base() const
Return the overlay part of this terrain, on the default_base().
Definition: terrain.cpp:286
umap_retval_pair_t insert(const unit_ptr &p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:135
This class represents a single unit of a specific type.
Definition: unit.hpp:39
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
Definition: unit.hpp:107
map_display and display: classes which take care of displaying the map and game-data on the screen.
Editor action classes.
#define LOG_ED
lg::log_domain log_editor
#define ERR_ED
#define WRN_ED
const config * cfg
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _(const char *str)
Definition: gettext.hpp:97
bool unrenamable() const
Whether this unit can be renamed.
Definition: unit.hpp:342
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1943
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:534
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:286
int side() const
The side this unit belongs to.
Definition: unit.hpp:249
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:309
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1327
map_location::direction facing() const
The current direction this unit is facing within its hex.
Definition: unit.hpp:1343
bool loyal() const
Gets whether this unit is loyal - ie, it costs no upkeep.
Definition: unit.cpp:1740
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
#define log_scope2(domain, description)
Definition: log.hpp:276
std::map< std::string, config > traits_map
Manage the empty-palette in the editor.
Definition: action.cpp:31
static std::string get_map_location(const std::string &file_contents, const std::string &attr)
Definition: map_context.cpp:98
std::deque< std::unique_ptr< editor_action > > action_stack
Action stack typedef.
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
bool is_cfg(const std::string &filename)
Returns true if the file ends with the wmlfile extension.
std::string base_name(const std::string &file, const bool remove_extension)
Returns the base filename of a file, with directory name stripped.
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:341
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
utils::optional< std::string > get_wml_location(const std::string &path, const utils::optional< std::string > &current_dir)
Returns a translated path to the actual file or directory, if it exists.
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
const std::string map_extension
Definition: filesystem.hpp:79
bool is_mask(const std::string &filename)
Returns true if the file ends with the maskfile extension.
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
std::string get_short_wml_path(const std::string &filename)
Returns a short path to filename, skipping the (user) data directory.
std::string directory_name(const std::string &file)
Returns the directory name of a file, with filename stripped.
bool is_map(const std::string &filename)
Returns true if the file ends with the mapfile extension.
std::string get_next_filename(const std::string &name, const std::string &extension)
Get the next free filename using "name + number (3 digits) + extension" maximum 1000 files then start...
Definition: filesystem.cpp:601
std::string get_current_editor_dir(const std::string &addon_id)
std::string path
Definition: filesystem.cpp:106
int village_income
Definition: game_config.cpp:41
int village_support
Definition: game_config.cpp:42
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:148
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:600
logger & info()
Definition: log.cpp:351
::tod_manager * tod_manager
Definition: resources.cpp:29
constexpr ter_layer NO_LAYER
Definition: translation.hpp:40
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
void trim(std::string_view &s)
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::map< std::string, t_string > string_map
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
filesystem::scoped_istream preprocess_string(const std::string &contents, const std::string &textdomain)
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map &defines)
Function to use the WML preprocessor on a file.
std::map< std::string, struct preproc_define > preproc_map
std::string filename
Filename.
editor_team_info(const team &t)
Definition: map_context.cpp:39
An exception object used when an IO error occurs.
Definition: filesystem.hpp:67
Encapsulates the map of the game.
Definition: location.hpp:46
static std::string write_direction(direction dir)
Definition: location.cpp:154
void write(config &cfg) const
Definition: location.cpp:223
std::string image
Definition: overlay.hpp:55
std::string team_name
Definition: overlay.hpp:57
float submerge
Definition: overlay.hpp:63
t_string name
Definition: overlay.hpp:58
std::string id
Definition: overlay.hpp:59
std::string halo
Definition: overlay.hpp:56
bool visible_in_fog
Definition: overlay.hpp:62
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
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
#define e