16 #define GETTEXT_DOMAIN "wesnoth-editor"
35 #include <boost/algorithm/string/predicate.hpp>
36 #include <boost/algorithm/string/trim.hpp>
37 #include <boost/regex.hpp>
44 , name(
t.user_team_name())
51 , shroud(
t.uses_shroud())
52 , share_vision(
t.share_vision())
54 , no_leader(
t.no_leader())
62 static const int editor_team_default_gold = 100;
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_()
84 , scenario_description_()
86 , victory_defeated_(true)
94 , game_classification_()
96 , last_map_generator_(nullptr)
101 static std::string
get_map_location(
const std::string& file_contents,
const std::string& attr)
103 std::size_t attr_name_start = file_contents.find(attr);
104 if(attr_name_start == std::string::npos)
return "";
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 "";
111 std::string attr_value = file_contents.substr(attr_value_start, line_end - attr_value_start);
113 return boost::trim_copy(attr_value);
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)
135 , scenario_description_()
137 , victory_defeated_(true)
138 , random_time_(false)
145 , game_classification_()
147 , last_map_generator_(nullptr)
194 if(file_string.empty()) {
195 std::string message =
_(
"Empty file");
204 std::string message =
_(
"File does not have .map, .cfg, or .mask extension");
211 LOG_ED <<
"Loading map or mask file";
220 LOG_ED <<
"Try loading scenario file";
224 }
catch(
const std::exception&
e) {
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);
233 if(!map_data_loc.empty()) {
234 if(map_data_loc.find(
"\"{") == std::string::npos) {
236 LOG_ED <<
"Loading embedded map_data";
240 std::size_t
start = file_string.find(map_data_loc)+1;
241 std::size_t length = file_string.find(
"\"",
start)-
start;
242 std::string map_data = file_string.substr(
start, length);
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 <<
"}";
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;
257 LOG_ED <<
"New filename is: " << new_filename.value();
263 }
else if(!map_file_loc.empty()) {
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;
272 LOG_ED <<
"New filename is: " << new_filename;
300 cfg[
"hidden"] =
false;
301 cfg[
"gold"] = editor_team_default_gold;
310 assert(
teams_.size() >=
static_cast<unsigned int>(
info.side));
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);
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);
330 const std::string& name,
331 const std::string& description,
334 bool victory_defeated,
383 std::string map_data = multiplayer[
"map_data"];
387 if(!map_data.empty()) {
398 ERR_ED <<
"Cannot convert " <<
filename_ <<
" due to missing map_data attribute.";
403 event[
"name"] =
"prestart";
404 event[
"id"] =
"editor_event-prestart";
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") {
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");
429 if(side_key ==
"unit") {
430 c1[
"side"] = child_cfg[
"side"];
434 }
else if(child_key ==
"time") {
436 c.append_attributes(child_cfg);
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);
457 }
else if(scen.
has_child(
"multiplayer")) {
462 ERR_ED <<
"Found no [scenario], [multiplayer], or [test] tag in " <<
filename_ <<
", assuming old-style editor scenario and defaulting to [multiplayer]";
472 xp_mod_ = experience_modifier->to_int();
475 random_time_ = scenario[
"random_start_time"].to_bool(
false);
477 if(!scenario[
"map_data"].str().empty()) {
479 }
else if(!scenario[
"map_file"].str().empty()) {
488 if(!side[
"recruit"].str().empty()) {
495 auto event = scenario.
find_child(
"event",
"id",
"editor_event-start");
497 event = scenario.
find_child(
"event",
"id",
"editor_event-prestart");
500 config& evt =
event.value();
560 LOG_ED <<
"Attempted to draw terrain off the map (" <<
loc <<
")";
566 if(terrain != old_terrain) {
569 }
else if(one_layer_only) {
645 std::string current_textdomain =
"wesnoth-"+
addon_id_;
667 return cfg[
"id"].str() ==
"editor_event-start" ||
cfg[
"id"].str() ==
"editor_event-prestart";
676 scenario[
"experience_modifier"] = *
xp_mod_;
689 event[
"name"] =
"prestart";
690 event[
"id"] =
"editor_event-prestart";
691 event[
"priority"] = 1000;
701 if(times[
"turns"].to_int() == -1) {
705 if(times[
"current_time"].to_int() == 0) {
713 event.append_children_by_move(times,
"time_area");
719 for(
const auto& overlay_pair :
overlays_) {
720 for(
const overlay& o : overlay_pair.second) {
724 overlay_pair.first.write(item);
727 item[
"image"] = o.
image;
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);
743 track->write(event,
true);
760 if(!boost::regex_match(
unit.
id(), boost::regex(
".*-[0-9]+"))) {
795 side[
"faction"] =
"Custom";
806 village.write(side.
add_child(
"village"));
817 std::string current_textdomain =
"wesnoth-"+
addon_id_;
834 symbols[
"msg"] =
e.what();
835 const std::string
msg =
VGETTEXT(
"Could not save time schedule: $msg", symbols);
841 editor_times[
"id"] = schedule_id;
842 editor_times[
"name"] =
t_string(schedule_name, current_textdomain);
851 std::stringstream wml_stream;
854 <<
"#textdomain " << current_textdomain <<
"\n"
856 <<
"# This file was generated using the scenario editor.\n"
858 <<
"#ifdef EDITOR\n";
865 wml_stream <<
"#endif";
867 if(!wml_stream.str().empty()) {
874 symbols[
"msg"] =
e.what();
875 const std::string
msg =
VGETTEXT(
"Could not save time schedule: $msg", symbols);
893 std::stringstream wml_stream;
895 <<
"# This file was generated using the scenario editor.\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"
907 if(!wml_stream.str().empty()) {
914 symbols[
"msg"] =
e.what();
915 const std::string
msg =
VGETTEXT(
"Could not save the scenario: $msg", symbols);
934 boost::regex rexpression_map_data(R
"((.*map_data\s*=\s*")(.+?)(".*))");
935 boost::smatch matched_map_data;
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];
942 ss << matched_map_data[3];
955 symbols[
"msg"] =
e.what();
956 const std::string
msg =
VGETTEXT(
"Could not save the map: $msg", symbols);
975 LOG_ED <<
"Performing action " << action.
get_id() <<
": " << action.
get_name() <<
", actions count is "
995 LOG_ED <<
"Performing (partial) action " << action.
get_id() <<
": " << action.
get_name() <<
", actions count is "
1002 if(undo_chain ==
nullptr) {
1067 WRN_ED <<
"undo() called with an empty undo stack";
1081 WRN_ED <<
"redo() called with an empty redo stack";
1095 if(undo_chain ==
nullptr) {
1102 if(undo_chain->
empty()) {
1107 redo_stack_.emplace_back(first_action_in_chain->perform(*
this));
1126 assert(!from.empty());
1128 std::unique_ptr<editor_action> action;
1129 action.swap(from.back());
1133 auto reverse_action = action->perform(*
this);
1134 to.emplace_back(std::move(reverse_action));
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.
void remove_attribute(std::string_view key)
config & add_child(std::string_view key)
void append(const config &cfg)
Append data from another config object to this one.
void remove_attributes(T... keys)
void append_children_by_move(config &cfg, std::string_view key)
Moves children with the given name from the given config to this one.
auto all_children_view() const
In-order iteration over all children.
void merge_attributes(const config &)
child_itors child_range(std::string_view key)
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.
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.
void append_attributes(const config &cfg)
Adds attributes from cfg.
std::size_t child_count(std::string_view key) const
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
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.
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.
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Container action wrapping several actions into one.
std::unique_ptr< editor_action > pop_first_action()
Remove the first added action and return it, transferring ownership to the caller.
void prepend_action(std::unique_ptr< editor_action > a)
Add an action at the beginning of the chain.
Base class for all editor actions.
int get_id() const
Debugging aid.
virtual const std::string & get_name() const
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 ...
static int get_instance_count()
Debugging aid.
This class adds extra editor-specific functionality to a normal gamemap.
std::set< map_location > set_starting_position_labels(display &disp)
Set labels for staring positions in the given display object.
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_...
void set_selection(const std::set< map_location > &area)
Select the given area.
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 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)
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.
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.
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.
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.
void set_everything_changed()
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.
bool victory_defeated() const
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.
int w() const
Effective map width.
int h() const
Effective map height.
bool on_board_with_border(const map_location &loc) const
std::string write() const
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.
const terrain_type & get_terrain_info(const t_translation::terrain_code &terrain) const
@ yes_no_buttons
Shows a yes and no button.
@ auto_close
Enables auto close.
void write(config &res) const
void read(const config &cfg)
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).
const std::string & team_name() const
team_shared_vision::type share_vision() const
const std::set< map_location > & villages() const
side_controller::type controller() const
const std::set< std::string > & recruits() const
const t_string & user_team_name() const
t_translation::terrain_code terrain_with_default_base() const
Return the overlay part of this terrain, on the default_base().
umap_retval_pair_t insert(const unit_ptr &p)
Inserts the unit pointed to by p into the map.
This class represents a single unit of a specific type.
static unit_ptr create(const config &cfg, bool use_traits=false, const vconfig *vcfg=nullptr)
Initializes a unit from a config.
map_display and display: classes which take care of displaying the map and game-data on the screen.
lg::log_domain log_editor
Declarations for File-IO.
static std::string _(const char *str)
bool unrenamable() const
Whether this unit can be renamed.
const std::string & type_id() const
The id of this unit's type.
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
const std::string & id() const
Gets this unit's id.
int side() const
The side this unit belongs to.
const t_string & name() const
Gets this unit's translatable display name.
const map_location & get_location() const
The current map location this unit is at.
map_location::direction facing() const
The current direction this unit is facing within its hex.
bool loyal() const
Gets whether this unit is loyal - ie, it costs no upkeep.
std::string id
Text to match against addon_info.tags()
#define log_scope2(domain, description)
std::map< std::string, config > traits_map
Manage the empty-palette in the editor.
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)
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 > ¤t_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
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...
std::string get_current_editor_dir(const std::string &addon_id)
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.
@ OK
Dialog was closed with the OK button.
config read(std::istream &in, abstract_validator *validator)
::tod_manager * tod_manager
constexpr ter_layer NO_LAYER
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.
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.
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="")
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)
An exception object used when an IO error occurs.
Encapsulates the map of the game.
static std::string write_direction(direction dir)
void write(config &cfg) const
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...