The Battle for Wesnoth  1.19.20+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/algorithm/string/predicate.hpp>
36 #include <boost/algorithm/string/trim.hpp>
37 #include <boost/regex.hpp>
38 
39 namespace editor
40 {
42  : side(t.side())
43  , id(t.team_name())
44  , name(t.user_team_name())
45  , recruit_list(utils::join(t.recruits(), ","))
46  , gold(t.gold())
47  , income(t.base_income())
48  , village_income(t.village_gold())
50  , fog(t.uses_fog())
51  , shroud(t.uses_shroud())
52  , share_vision(t.share_vision())
54  , no_leader(t.no_leader())
55  , hidden(t.hidden())
56 {
57 }
58 
59 const std::size_t map_context::max_action_stack_size_ = 100;
60 
61 namespace {
62  static const int editor_team_default_gold = 100;
63 }
64 
65 map_context::map_context(const editor_map& map, bool pure_map, const std::string& addon_id)
66  : filename_()
67  , map_data_key_()
68  , embedded_(false)
69  , pure_map_(pure_map)
70  , map_(map)
71  , undo_stack_()
72  , redo_stack_()
73  , actions_since_save_(0)
74  , needs_reload_(false)
75  , needs_terrain_rebuild_(false)
76  , needs_labels_reset_(false)
77  , everything_changed_(false)
78  , changed_locations_()
79  , starting_position_label_locs_()
80  , addon_id_(addon_id)
81  , previous_cfg_()
82  , scenario_id_()
83  , scenario_name_()
84  , scenario_description_()
85  , xp_mod_()
86  , victory_defeated_(true)
87  , random_time_(false)
88  , active_area_(-1)
89  , labels_(nullptr)
90  , units_()
91  , teams_()
92  , tod_manager_(new tod_manager)
93  , mp_settings_()
94  , game_classification_()
95  , music_tracks_()
96  , last_map_generator_(nullptr)
97 {
98 }
99 
100 // Look for attr in file_contents, if there is an equal sign on the same line assume that what follows to the line end is the attribute value
101 static std::string get_map_location(const std::string& file_contents, const std::string& attr)
102 {
103  std::size_t attr_name_start = file_contents.find(attr);
104  if(attr_name_start == std::string::npos) return "";
105 
106  std::size_t attr_value_start = file_contents.find("=", attr_name_start);
107  std::size_t line_end = file_contents.find("\n", attr_name_start);
108  if(line_end < attr_value_start) return ""; // assume not both values are string::npos
109 
110  attr_value_start++;
111  std::string attr_value = file_contents.substr(attr_value_start, line_end - attr_value_start);
112 
113  return boost::trim_copy(attr_value);
114 }
115 
116 map_context::map_context(const std::string& filename, const std::string& addon_id)
118  , map_data_key_()
119  , embedded_(false)
120  , pure_map_(false)
121  , map_()
122  , undo_stack_()
123  , redo_stack_()
124  , actions_since_save_(0)
125  , needs_reload_(false)
126  , needs_terrain_rebuild_(false)
127  , needs_labels_reset_(false)
128  , everything_changed_(false)
129  , changed_locations_()
130  , starting_position_label_locs_()
131  , addon_id_(addon_id)
132  , previous_cfg_()
133  , scenario_id_()
134  , scenario_name_()
135  , scenario_description_()
136  , xp_mod_()
137  , victory_defeated_(true)
138  , random_time_(false)
139  , active_area_(-1)
140  , labels_(nullptr)
141  , units_()
142  , teams_()
143  , tod_manager_(new tod_manager)
144  , mp_settings_()
145  , game_classification_()
146  , music_tracks_()
147  , last_map_generator_(nullptr)
148 {
149  /*
150  * Overview of situations possibly found in the file:
151  *
152  * embedded_ - the map data is directly in the scenario file
153  * pure_map_ - the map data is in its own separate file (map_file, map_data+macro inclusion) or this is a .map file
154  *
155  * 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
156  *
157  * 0. Not a scenario or map file.
158  * 0.1 File not found
159  * 0.2 Map file empty
160  * 0.3 Not a .map or .cfg file
161  * 1. It's a .map file.
162  * * embedded_ = false
163  * * pure_map_ = true
164  * 2. A scenario embedding the map
165  * * embedded_ = true
166  * * pure_map_ = true
167  * The scenario-test.cfg for example.
168  * The map is written back to the file.
169  * 3. The map file is referenced by map_data={MACRO_ARGUEMENT}.
170  * * embedded_ = false
171  * * pure_map_ = true
172  * 4. The file contains an editor generated scenario file.
173  * * embedded_ = false
174  * * pure_map_ = false
175  * 5. The file is using map_file.
176  * 5.1 The file doesn't contain a macro and so can be loaded by the editor as a scenario
177  * * embedded_ = false
178  * * pure_map_ = false
179  * 5.2 The file contains a macro and so can't be loaded by the editor as a scenario
180  * * embedded_ = false
181  * * pure_map_ = true
182  */
183 
184  log_scope2(log_editor, "Loading file " + filename);
185 
186  // 0.1 File not found
188  throw editor_map_load_exception(filename, _("File not found"));
189  }
190 
191  std::string file_string = filesystem::read_file(filename);
192 
193  // 0.2 Map file empty
194  if(file_string.empty()) {
195  std::string message = _("Empty file");
196  throw editor_map_load_exception(filename, message);
197  }
198 
199  // 0.3 Not a .map or .cfg file
203  {
204  std::string message = _("File does not have .map, .cfg, or .mask extension");
205  throw editor_map_load_exception(filename, message);
206  }
207 
208  // 1.0 Pure map data
211  LOG_ED << "Loading map or mask file";
212  map_ = editor_map::from_string(file_string); // throws on error
213  pure_map_ = true;
214 
216  return;
217  }
218 
219  // 2.0 try loading cfg file
220  LOG_ED << "Try loading scenario file";
221  bool loaded = false;
222  try {
223  loaded = load_scenario();
224  } catch(const std::exception& e) {
225  utils::string_map symbols;
226  symbols["error"] = e.what();
227  const std::string msg = VGETTEXT("Failed to load the scenario: $error\n\nAttempting to load only the map.", symbols);
229 
230  std::string map_data_loc = get_map_location(file_string, "map_data");
231  std::string map_file_loc = get_map_location(file_string, "map_file");
232 
233  if(!map_data_loc.empty()) {
234  if(map_data_loc.find("\"{") == std::string::npos) {
235  // 2.1 Embedded pure map
236  LOG_ED << "Loading embedded map_data";
237  embedded_ = true;
238  pure_map_ = true;
239  // build on sloppy parsing from get_map_location
240  std::size_t start = file_string.find(map_data_loc)+1; // assume that the "parsed" attribute value is not found earlier in the scenario file and assume it starts with a double quote
241  std::size_t length = file_string.find("\"", start)-start; // assume the attribute value ends with a double quote and does not contain one
242  std::string map_data = file_string.substr(start, length);
243  map_ = editor_map::from_string(map_data);
244  } else {
245  // 2.2 Macro referenced pure map
246  // assume `map_data = "{...}"` so skip the quotes and curly braces
247  const std::string& macro_argument = map_data_loc.substr(2, map_data_loc.size()-4);
248  LOG_ED << "Map looks like a scenario, trying {" << macro_argument << "}";
249 
251 
252  if(!new_filename) {
253  std::string message = _("The file looks like a scenario, but the map_data attribute does not point to an existing file") + std::string("\n") + macro_argument;
254  throw editor_map_load_exception(filename, message);
255  }
256 
257  LOG_ED << "New filename is: " << new_filename.value();
258 
259  pure_map_ = true;
260  filename_ = new_filename.value();
262  }
263  } else if(!map_file_loc.empty()) {
264  // 2.3 map_file referenced pure map
265 
266  const std::string& new_filename = filesystem::get_map_file(map_file_loc);
267  if(!filesystem::file_exists(new_filename)) {
268  std::string message = _("The file looks like a scenario, but the map_file attribute does not point to an existing file") + std::string("\n") + map_file_loc;
269  throw editor_map_load_exception(filename, message);
270  }
271 
272  LOG_ED << "New filename is: " << new_filename;
273 
274  pure_map_ = true;
275  filename_ = new_filename;
277  } else {
278  // 2.4 could not load the scenario or find the map data in it; throw error from load_scenario
279  throw editor_map_load_exception(filename, e.what());
280  }
281  }
282  if(!loaded) {
283  throw editor_map_load_exception(filename, "Aborted");
284  }
286 }
287 
289 {
290  undo_stack_.clear();
291  redo_stack_.clear();
292 }
293 
295 {
296  teams_.emplace_back();
297 
298  config cfg;
299  cfg["side"] = teams_.size(); // side is 1-indexed, so we can just use size()
300  cfg["hidden"] = false;
301  cfg["gold"] = editor_team_default_gold;
302 
303  teams_.back().build(cfg, map());
304 
306 }
307 
309 {
310  assert(teams_.size() >= static_cast<unsigned int>(info.side));
311 
312  team& t = teams_[info.side - 1];
313  t.change_team(info.id, info.name);
314  t.set_recruits(utils::split_set(info.recruit_list, ','));
315  t.have_leader(!info.no_leader);
316  t.change_controller(info.controller);
317  t.set_gold(info.gold);
318  t.set_base_income(info.income);
319  t.set_hidden(info.hidden);
320  t.set_fog(info.fog);
321  t.set_shroud(info.shroud);
322  t.set_share_vision(info.share_vision);
323  t.set_village_gold(info.village_income);
324  t.set_village_support(info.village_support);
325 
327 }
328 
329 void map_context::set_scenario_setup(const std::string& id,
330  const std::string& name,
331  const std::string& description,
332  int turns,
333  int xp_mod,
334  bool victory_defeated,
335  bool random_time)
336 {
337  scenario_id_ = id;
338  scenario_name_ = name;
339  scenario_description_ = description;
340  random_time_ = random_time;
342  tod_manager_->set_number_of_turns(turns);
343  xp_mod_ = xp_mod;
345 }
346 
348 {
349  tod_manager_->set_current_time(time);
350  if(!pure_map_) {
352  }
353 }
354 
356 {
357  tod_manager_->remove_time_area(index);
358  active_area_--;
360 }
361 
362 void map_context::replace_schedule(const std::vector<time_of_day>& schedule)
363 {
364  tod_manager_->replace_schedule(schedule);
365  if(!pure_map_) {
367  }
368 }
369 
370 void map_context::replace_local_schedule(const std::vector<time_of_day>& schedule)
371 {
372  tod_manager_->replace_local_schedule(schedule, active_area_);
373  if(!pure_map_) {
375  }
376 }
377 
379 {
380  config cfg;
381  config& multiplayer = cfg.add_child("multiplayer");
382  multiplayer.append_attributes(old_scenario);
383  std::string map_data = multiplayer["map_data"];
384  std::string separate_map_file = filesystem::get_current_editor_dir(addon_id_) + "/maps/" + filesystem::base_name(filename_, true) + filesystem::map_extension;
385 
386  // check that there's embedded map data, since that's how the editor used to save scenarios
387  if(!map_data.empty()) {
388  // 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
389  if(filesystem::file_exists(separate_map_file)) {
391  }
392  multiplayer["id"] = filesystem::base_name(separate_map_file, true);
393 
394  filesystem::write_file(separate_map_file, map_data);
395  multiplayer.remove_attribute("map_data");
396  multiplayer["map_file"] = filesystem::base_name(separate_map_file);
397  } else {
398  ERR_ED << "Cannot convert " << filename_ << " due to missing map_data attribute.";
399  throw editor_map_load_exception("load_scenario: no embedded map_data attribute found in old-style scenario", filename_);
400  }
401 
402  config& event = multiplayer.add_child("event");
403  event["name"] = "prestart";
404  event["id"] = "editor_event-prestart";
405 
406  // for all children that aren't [side] or [time], move them to an event
407  // for [side]:
408  // keep all attributes in [side]
409  // also keep any [village]s in [side]
410  // move all other children to the start [event]
411  // if [unit], set the unit's side
412  // for [time]:
413  // keep under [multiplayer]
414  for(const auto [child_key, child_cfg]: old_scenario.all_children_view()) {
415  if(child_key != "side" && child_key != "time") {
416  config& c = event.add_child(child_key);
417  c.append_attributes(child_cfg);
418  c.append_children(child_cfg);
419  } else if(child_key == "side") {
420  config& c = multiplayer.add_child("side");
421  c.append_attributes(child_cfg);
422  for(const auto [side_key, side_cfg] : child_cfg.all_children_view()) {
423  if(side_key == "village") {
424  config& c1 = c.add_child("village");
425  c1.append_attributes(side_cfg);
426  } else {
427  config& c1 = event.add_child(side_key);
428  c1.append_attributes(side_cfg);
429  if(side_key == "unit") {
430  c1["side"] = child_cfg["side"];
431  }
432  }
433  }
434  } else if(child_key == "time") {
435  config& c = multiplayer.add_child("time");
436  c.append_attributes(child_cfg);
437  }
438  }
439 
440  return cfg;
441 }
442 
444 {
445  config scen = io::read(*preprocess_file(filename_)); // this call might throw anyway so ask only after trying
446  std::string scenario_text = filesystem::read_file(filename_);
447  if(!boost::algorithm::starts_with(scenario_text, "# This file was generated using the scenario editor.")) {
448  int res = gui2::show_message(_("Confirm"), "This file was not generated by the scenario editor. Saving it may have unwanted consequences including removal of all preprocessor constructs like comments and macro use. Do you want to continue?", gui2::dialogs::message::yes_no_buttons);
449  if(res != gui2::retval::OK) {
450  return false;
451  }
452  }
453 
454  config scenario;
455  if(scen.has_child("scenario")) {
456  scenario = scen.mandatory_child("scenario");
457  } else if(scen.has_child("multiplayer")) {
458  scenario = scen.mandatory_child("multiplayer");
459  } else if(scen.has_child("test")) {
460  scenario = scen.mandatory_child("test");
461  } else {
462  ERR_ED << "Found no [scenario], [multiplayer], or [test] tag in " << filename_ << ", assuming old-style editor scenario and defaulting to [multiplayer]";
463  scen = convert_scenario(scen);
464  scenario = scen.mandatory_child("multiplayer");
465  }
466 
467  scenario_id_ = scenario["id"].str();
468  scenario_name_ = scenario["name"].str();
469  scenario_description_ = scenario["description"].str();
470 
471  if(const config::attribute_value* experience_modifier = scenario.get("experience_modifier")) {
472  xp_mod_ = experience_modifier->to_int();
473  }
474  victory_defeated_ = scenario["victory_when_enemies_defeated"].to_bool(true);
475  random_time_ = scenario["random_start_time"].to_bool(false);
476 
477  if(!scenario["map_data"].str().empty()) {
478  map_ = editor_map::from_string(scenario["map_data"].str()); // throws on error
479  } else if(!scenario["map_file"].str().empty()) {
481  } else {
482  throw editor_map_load_exception("load_scenario: no map_file or map_data attribute found", filename_);
483  }
484 
485  for(config& side : scenario.child_range("side")) {
486  teams_.emplace_back();
487  teams_.back().build(side, map_);
488  if(!side["recruit"].str().empty()) {
489  teams_.back().set_recruits(utils::split_set(side["recruit"].str(), ','));
490  }
491  }
492 
493  tod_manager_.reset(new tod_manager(scenario));
494 
495  auto event = scenario.find_child("event", "id", "editor_event-start");
496  if(!event) {
497  event = scenario.find_child("event", "id", "editor_event-prestart");
498  }
499  if(event) {
500  config& evt = event.value();
501 
502  labels_.read(evt);
503 
504  for(const config& time_area : evt.child_range("time_area")) {
505  tod_manager_->add_time_area(map_, time_area);
506  }
507 
508  for(const config& item : evt.child_range("item")) {
509  const map_location loc(item);
510  overlays_[loc].push_back(overlay(item));
511  }
512 
513  for(const config& music : evt.child_range("music")) {
514  music_tracks_.emplace_back(sound::music_track::create(music));
515  }
516 
517  for(config& a_unit : evt.child_range("unit")) {
518  units_.insert(unit::create(a_unit, true));
519  }
520  }
521 
522  previous_cfg_ = scen;
523  return true;
524 }
525 
527 {
528  map_.set_selection(tod_manager_->get_area_by_index(index));
529 }
530 
531 bool map_context::playlist_contains(const std::shared_ptr<sound::music_track>& track) const
532 {
533  return utils::contains(music_tracks_, track);
534 }
535 
536 void map_context::toggle_track(const std::shared_ptr<sound::music_track>& track)
537 {
538  if(playlist_contains(track)) {
539  music_tracks_.remove(track);
540  } else {
541  music_tracks_.push_back(track);
542  }
543 }
544 
545 void map_context::draw_terrain(const t_translation::terrain_code& terrain, const map_location& loc, bool one_layer_only)
546 {
547  t_translation::terrain_code full_terrain = one_layer_only
548  ? terrain
550 
551  draw_terrain_actual(full_terrain, loc, one_layer_only);
552 }
553 
555  const t_translation::terrain_code& terrain, const map_location& loc, bool one_layer_only)
556 {
558  // requests for painting off the map are ignored in set_terrain anyway,
559  // but ideally we should not have any
560  LOG_ED << "Attempted to draw terrain off the map (" << loc << ")";
561  return;
562  }
563 
565 
566  if(terrain != old_terrain) {
567  if(terrain.base == t_translation::NO_LAYER) {
569  } else if(one_layer_only) {
571  } else {
572  map_.set_terrain(loc, terrain);
573  }
574 
576  }
577 }
578 
580  const t_translation::terrain_code& terrain, const std::set<map_location>& locs, bool one_layer_only)
581 {
582  t_translation::terrain_code full_terrain = one_layer_only
583  ? terrain
585 
586  for(const map_location& loc : locs) {
587  draw_terrain_actual(full_terrain, loc, one_layer_only);
588  }
589 }
590 
592 {
593  everything_changed_ = false;
594  changed_locations_.clear();
595 }
596 
598 {
599  if(!everything_changed()) {
600  changed_locations_.insert(loc);
601  }
602 }
603 
604 void map_context::add_changed_location(const std::set<map_location>& locs)
605 {
606  if(!everything_changed()) {
607  changed_locations_.insert(locs.begin(), locs.end());
608  }
609 }
610 
612 {
613  everything_changed_ = true;
614 }
615 
617 {
618  return everything_changed_;
619 }
620 
622 {
623  disp.labels().clear_all();
625 }
626 
628 {
629  std::set<map_location> new_label_locs = map_.set_starting_position_labels(disp);
630  starting_position_label_locs_.insert(new_label_locs.begin(), new_label_locs.end());
631 }
632 
634 {
637  set_needs_labels_reset(false);
638 }
639 
641 {
642  config scen;
643 
644  // Textdomain
645  std::string current_textdomain = "wesnoth-"+addon_id_;
646 
647  // the state of the previous scenario cfg
648  // if it exists, alter specific parts of it (sides, times, and editor events) rather than replacing it entirely
649  if(previous_cfg_) {
650  scen = *previous_cfg_;
651  }
652 
653  // if this has [multiplayer], use [multiplayer]
654  // else if this has [scenario], use [scenario]
655  // else if this has [test], use [test]
656  // else if none, add a [multiplayer]
657  config& scenario = scen.has_child("multiplayer")
658  ? scen.mandatory_child("multiplayer")
659  : scen.has_child("scenario")
660  ? scen.mandatory_child("scenario")
661  : scen.has_child("test")
662  ? scen.mandatory_child("test")
663  : scen.add_child("multiplayer");
664 
665  scenario.remove_children("side");
666  scenario.remove_children("event", [](const config& cfg) {
667  return cfg["id"].str() == "editor_event-start" || cfg["id"].str() == "editor_event-prestart";
668  });
669  scenario.remove_children("time");
670 
671  scenario["id"] = scenario_id_;
672  scenario["name"] = t_string(scenario_name_, current_textdomain);
673  scenario["description"] = t_string(scenario_description_, current_textdomain);
674 
675  if(xp_mod_) {
676  scenario["experience_modifier"] = *xp_mod_;
677  }
678  if(victory_defeated_) {
679  scenario["victory_when_enemies_defeated"] = *victory_defeated_;
680  }
681  scenario["random_start_time"] = random_time_;
682 
683  // write out the map data
684  scenario["map_file"] = scenario_id_ + filesystem::map_extension;
686 
687  // find or add the editor's start event
688  config& event = scenario.add_child("event");
689  event["name"] = "prestart";
690  event["id"] = "editor_event-prestart";
691  event["priority"] = 1000;
692 
693  // write out all the scenario data below
694 
695  // [time]s and [time_area]s
696  config times = tod_manager_->to_config(current_textdomain);
697 
698  // TODO: random_start_time is written separately above. Should we use the value from the ToD manager?
699  times.remove_attributes("turn_at", "it_is_a_new_turn", "random_start_time");
700 
701  if(times["turns"].to_int() == -1) {
702  times.remove_attribute("turns");
703  }
704 
705  if(times["current_time"].to_int() == 0) {
706  times.remove_attribute("current_time");
707  }
708 
709  scenario.merge_attributes(times);
710  scenario.append_children_by_move(times, "time");
711 
712  // put the [time_area]s into the event to keep as much editor-specific stuff separated in its own event as possible
713  event.append_children_by_move(times, "time_area");
714 
715  // [label]s
716  labels_.write(event);
717 
718  // [item]s
719  for(const auto& overlay_pair : overlays_) {
720  for(const overlay& o : overlay_pair.second) {
721  config& item = event.add_child("item");
722 
723  // Write x,y location
724  overlay_pair.first.write(item);
725 
726  // These should always have a value
727  item["image"] = o.image;
728  item["visible_in_fog"] = o.visible_in_fog;
729 
730  // Optional keys
731  item["id"].write_if_not_empty(o.id);
732  item["name"].write_if_not_empty(t_string(o.name, current_textdomain));
733  item["team_name"].write_if_not_empty(o.team_name);
734  item["halo"].write_if_not_empty(o.halo);
735  if(o.submerge) {
736  item["submerge"] = o.submerge;
737  }
738  }
739  }
740 
741  // [music]s
742  for(const auto& track : music_tracks_) {
743  track->write(event, true);
744  }
745 
746  // [unit]s
748  preprocess_file(game_config::path + "/data/core/macros/traits.cfg", traits_map);
749 
750  for(const auto& unit : units_) {
751  config& u = event.add_child("unit");
752 
753  unit.get_location().write(u);
754 
755  u["side"] = unit.side();
756  u["type"] = unit.type_id();
757  u["name"].write_if_not_empty(t_string(unit.name(), current_textdomain));
758  u["facing"] = map_location::write_direction(unit.facing());
759 
760  if(!boost::regex_match(unit.id(), boost::regex(".*-[0-9]+"))) {
761  u["id"] = unit.id();
762  }
763 
764  if(unit.can_recruit()) {
765  u["canrecruit"] = unit.can_recruit();
766  }
767 
768  if(unit.unrenamable()) {
769  u["unrenamable"] = unit.unrenamable();
770  }
771 
772  config& mods = u.add_child("modifications");
773  if(unit.loyal()) {
774  mods.append(io::read(*preprocess_string("{TRAIT_LOYAL}", "wesnoth-help", traits_map)));
775  }
776  //TODO this entire block could also be replaced by unit.write(u, true)
777  //however, the resultant config is massive and contains many attributes we don't need.
778  //need to find a middle ground here.
779  }
780 
781  // [side]s
782  for(const auto& team : teams_) {
783  config& side = scenario.add_child("side");
784 
785  side["side"] = scenario.child_count("side");
786  side["hidden"] = team.hidden();
787 
788  side["controller"] = side_controller::get_string(team.controller());
789  side["no_leader"] = team.no_leader();
790 
791  side["team_name"] = team.team_name();
792  side["user_team_name"].write_if_not_empty(t_string(team.user_team_name(), current_textdomain));
793  if(team.recruits().size() > 0) {
794  side["recruit"] = utils::join(team.recruits(), ",");
795  side["faction"] = "Custom";
796  }
797 
798  side["fog"] = team.uses_fog();
799  side["shroud"] = team.uses_shroud();
800  side["share_vision"] = team_shared_vision::get_string(team.share_vision());
801 
802  side["gold"] = team.gold();
803  side["income"] = team.raw_income();
804 
805  for(const map_location& village : team.villages()) {
806  village.write(side.add_child("village"));
807  }
808  }
809 
810  previous_cfg_ = scen;
811  return scen;
812 }
813 
814 void map_context::save_schedule(const std::string& schedule_id, const std::string& schedule_name)
815 {
816  // Textdomain
817  std::string current_textdomain = "wesnoth-"+addon_id_;
818 
819  // Path to schedule.cfg
820  std::string schedule_path = filesystem::get_current_editor_dir(addon_id_) + "/utils/schedule.cfg";
821 
822  // Create schedule config
823  config schedule;
824  try {
825  if (filesystem::file_exists(schedule_path)) {
826  /* If exists, read the schedule.cfg
827  * and insert [editor_times] block at correct place */
829  editor_map.try_emplace("EDITOR");
830  schedule = io::read(*preprocess_file(schedule_path, editor_map));
831  }
832  } catch(const filesystem::io_exception& e) {
833  utils::string_map symbols;
834  symbols["msg"] = e.what();
835  const std::string msg = VGETTEXT("Could not save time schedule: $msg", symbols);
837  }
838 
839  config& editor_times = schedule.add_child("editor_times");
840 
841  editor_times["id"] = schedule_id;
842  editor_times["name"] = t_string(schedule_name, current_textdomain);
843  config times = tod_manager_->to_config(current_textdomain);
844  for(const config& time : times.child_range("time")) {
845  config& t = editor_times.add_child("time");
846  t.append(time);
847  }
848 
849  // Write to file
850  try {
851  std::stringstream wml_stream;
852 
853  wml_stream
854  << "#textdomain " << current_textdomain << "\n"
855  << "#\n"
856  << "# This file was generated using the scenario editor.\n"
857  << "#\n"
858  << "#ifdef EDITOR\n";
859 
860  {
861  config_writer out(wml_stream, false);
862  out.write(schedule);
863  }
864 
865  wml_stream << "#endif";
866 
867  if(!wml_stream.str().empty()) {
868  filesystem::write_file(schedule_path, wml_stream.str());
869  gui2::show_transient_message("", _("Time schedule saved."));
870  }
871 
872  } catch(const filesystem::io_exception& e) {
873  utils::string_map symbols;
874  symbols["msg"] = e.what();
875  const std::string msg = VGETTEXT("Could not save time schedule: $msg", symbols);
877  }
878 }
879 
881 {
882  assert(!is_embedded());
883 
884  if(scenario_id_.empty()) {
886  }
887 
888  if(scenario_name_.empty()) {
890  }
891 
892  try {
893  std::stringstream wml_stream;
894  wml_stream
895  << "# This file was generated using the scenario editor.\n"
896  << "#\n"
897  << "# If you edit this file by hand, then do not expect common macros to be available when the editor loads the file.\n"
898  << "# Also expect any preprocessor constructs (which includes macros and comments) to be expanded/vanish on saving.\n"
899  << "# 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"
900  << "# Any manual changes made to those will be lost.\n"
901  << "\n";
902  {
903  config_writer out(wml_stream, false);
904  out.write(to_config(), true);
905  }
906 
907  if(!wml_stream.str().empty()) {
908  filesystem::write_file(get_filename(), wml_stream.str());
909  }
910 
911  clear_modified();
912  } catch(const filesystem::io_exception& e) {
913  utils::string_map symbols;
914  symbols["msg"] = e.what();
915  const std::string msg = VGETTEXT("Could not save the scenario: $msg", symbols);
916 
918  }
919 
920  // After saving the map as a scenario, it's no longer a pure map.
921  pure_map_ = false;
922 }
923 
925 {
926  std::string map_data = map_.write();
927 
928  try {
929  if(!is_embedded()) {
931  } else {
932  std::string map_string = filesystem::read_file(get_filename());
933 
934  boost::regex rexpression_map_data(R"((.*map_data\s*=\s*")(.+?)(".*))");
935  boost::smatch matched_map_data;
936 
937  if(boost::regex_search(map_string, matched_map_data, rexpression_map_data,
938  boost::regex_constants::match_not_dot_null)) {
939  std::stringstream ss;
940  ss << matched_map_data[1];
941  ss << map_data;
942  ss << matched_map_data[3];
943 
945  } else {
946  throw editor_map_save_exception(_("Could not save into scenario"));
947  }
948  }
949 
951 
952  clear_modified();
953  } catch(const filesystem::io_exception& e) {
954  utils::string_map symbols;
955  symbols["msg"] = e.what();
956  const std::string msg = VGETTEXT("Could not save the map: $msg", symbols);
957 
959  }
960 }
961 
963 {
964  if(map_.h() != map.h() || map_.w() != map.w()) {
966  } else {
968  }
969 
970  map_ = map;
971 }
972 
974 {
975  LOG_ED << "Performing action " << action.get_id() << ": " << action.get_name() << ", actions count is "
976  << action.get_instance_count();
977  auto undo = action.perform(*this);
978  if(actions_since_save_ < 0) {
979  // set to a value that will make it impossible to get to zero, as at this point
980  // it is no longer possible to get back the original map state using undo/redo
981  actions_since_save_ = 1 + undo_stack_.size();
982  }
983 
985 
986  undo_stack_.emplace_back(std::move(undo));
987 
989 
990  redo_stack_.clear();
991 }
992 
994 {
995  LOG_ED << "Performing (partial) action " << action.get_id() << ": " << action.get_name() << ", actions count is "
996  << action.get_instance_count();
997  if(!can_undo()) {
998  throw editor_logic_exception("Empty undo stack in perform_partial_action()");
999  }
1000 
1001  editor_action_chain* undo_chain = dynamic_cast<editor_action_chain*>(last_undo_action());
1002  if(undo_chain == nullptr) {
1003  throw editor_logic_exception("Last undo action not a chain in perform_partial_action()");
1004  }
1005 
1006  auto undo = action.perform(*this);
1007 
1008  // actions_since_save_ += action.action_count();
1009  undo_chain->prepend_action(std::move(undo));
1010 
1011  redo_stack_.clear();
1012 }
1013 
1015 {
1016  return actions_since_save_ != 0;
1017 }
1018 
1020 {
1021  actions_since_save_ = 0;
1022 }
1023 
1025 {
1027 }
1028 
1030 {
1031  return !undo_stack_.empty();
1032 }
1033 
1035 {
1036  return !redo_stack_.empty();
1037 }
1038 
1040 {
1041  return undo_stack_.empty() ? nullptr : undo_stack_.back().get();
1042 }
1043 
1045 {
1046  return redo_stack_.empty() ? nullptr : redo_stack_.back().get();
1047 }
1048 
1050 {
1051  return undo_stack_.empty() ? nullptr : undo_stack_.back().get();
1052 }
1053 
1055 {
1056  return redo_stack_.empty() ? nullptr : redo_stack_.back().get();
1057 }
1058 
1060 {
1061  LOG_ED << "undo() beg, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1062 
1063  if(can_undo()) {
1066  } else {
1067  WRN_ED << "undo() called with an empty undo stack";
1068  }
1069 
1070  LOG_ED << "undo() end, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1071 }
1072 
1074 {
1075  LOG_ED << "redo() beg, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1076 
1077  if(can_redo()) {
1080  } else {
1081  WRN_ED << "redo() called with an empty redo stack";
1082  }
1083 
1084  LOG_ED << "redo() end, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size();
1085 }
1086 
1088 {
1089  // callers should check for these conditions
1090  if(!can_undo()) {
1091  throw editor_logic_exception("Empty undo stack in partial_undo()");
1092  }
1093 
1094  editor_action_chain* undo_chain = dynamic_cast<editor_action_chain*>(last_undo_action());
1095  if(undo_chain == nullptr) {
1096  throw editor_logic_exception("Last undo action not a chain in partial undo");
1097  }
1098 
1099  // a partial undo performs the first action form the current action's action_chain that would be normally performed
1100  // i.e. the *first* one.
1101  const auto first_action_in_chain = undo_chain->pop_first_action();
1102  if(undo_chain->empty()) {
1104  undo_stack_.pop_back();
1105  }
1106 
1107  redo_stack_.emplace_back(first_action_in_chain->perform(*this));
1108  // actions_since_save_ -= last_redo_action()->action_count();
1109 }
1110 
1112 {
1113  undo_stack_.clear();
1114  redo_stack_.clear();
1115 }
1116 
1118 {
1119  if(stack.size() > max_action_stack_size_) {
1120  stack.pop_front();
1121  }
1122 }
1123 
1125 {
1126  assert(!from.empty());
1127 
1128  std::unique_ptr<editor_action> action;
1129  action.swap(from.back());
1130 
1131  from.pop_back();
1132 
1133  auto reverse_action = action->perform(*this);
1134  to.emplace_back(std::move(reverse_action));
1135 
1136  trim_stack(to);
1137 }
1138 
1140 {
1141  return is_pure_map() ? _("New Map") : _("New Scenario");
1142 }
1143 
1144 } // 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, bool strong_quotes=false)
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:2428
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
std::set< map_location > set_starting_position_labels(display &disp)
Set labels for staring positions in the given display object.
Definition: editor_map.cpp:100
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:54
void set_selection(const std::set< map_location > &area)
Select the given area.
Definition: editor_map.cpp:132
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 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 select_area(int index)
Select the nth tod area.
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:265
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:352
std::string write() const
Definition: map.cpp:172
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
@ yes_no_buttons
Shows a yes and no button.
Definition: message.hpp:81
@ 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:415
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:269
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:1954
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:1751
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)
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:344
std::string get_map_file(const std::string &name)
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.cpp:279
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:604
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
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:610
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
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:41
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