18 #define GETTEXT_DOMAIN "wesnoth-lib"
47 #include <boost/algorithm/string/replace.hpp>
48 #include <boost/filesystem.hpp>
70 connect_signal<event::SDL_KEY_DOWN>(std::bind(
78 button& quit = find_widget<button>(
"exit");
85 menu_button& alignments = find_widget<menu_button>(
"alignment_list");
89 const std::string& icon_path =
"icons/alignments/alignment_" + std::string(align) +
"_30.png";
90 align_list_.emplace_back(
"label",
t_string(
static_cast<std::string
>(align),
"wesnoth"),
"icon", icon_path);
94 menu_button& races = find_widget<menu_button>(
"race_list");
96 const std::string& race_name =
i.second.id();
97 race_list_.emplace_back(
"label", race_name,
"icon",
i.second.get_icon_path_stem() +
"_30.png");
104 button&
load = find_widget<button>(
"load_unit_type");
116 find_widget<button>(
"browse_unit_image"),
119 find_widget<button>(
"preview_unit_image"),
122 find_widget<button>(
"browse_portrait_image"),
125 find_widget<button>(
"preview_portrait_image"),
129 find_widget<text_box>(
"name_box"),
132 find_widget<text_box>(
"id_box"),
139 menu_button& movetypes = find_widget<menu_button>(
"movetype_list");
148 menu_button& defenses = find_widget<menu_button>(
"defense_list");
157 menu_button& movement_costs = find_widget<menu_button>(
"movement_costs_list");
165 menu_button& resistances = find_widget<menu_button>(
"resistances_list");
172 resistances_list_.emplace_back(
"label", key,
"icon",
"icons/profiles/" + key +
".png");
180 menu_button& usage_types = find_widget<menu_button>(
"usage_list");
188 multimenu_button& abilities = find_widget<multimenu_button>(
"abilities_list");
192 find_widget<button>(
"browse_small_profile_image"),
195 find_widget<button>(
"preview_small_profile_image"),
198 find_widget<button>(
"load_movetype"),
201 find_widget<slider>(
"resistances_slider"),
204 find_widget<menu_button>(
"resistances_list"),
207 find_widget<toggle_button>(
"resistances_checkbox"),
211 find_widget<slider>(
"defense_slider"),
214 find_widget<menu_button>(
"defense_list"),
217 find_widget<toggle_button>(
"defense_checkbox"),
221 find_widget<slider>(
"movement_costs_slider"),
224 find_widget<menu_button>(
"movement_costs_list"),
227 find_widget<toggle_button>(
"movement_costs_checkbox"),
246 multimenu_button& specials = find_widget<multimenu_button>(
"weapon_specials_list");
249 combobox& attack_types = find_widget<combobox>(
"attack_type_list");
256 find_widget<button>(
"browse_attack_image"),
259 find_widget<button>(
"preview_attack_image"),
262 find_widget<menu_button>(
"atk_list"),
265 find_widget<button>(
"atk_new"),
268 find_widget<button>(
"atk_delete"),
271 find_widget<button>(
"atk_next"),
274 find_widget<button>(
"atk_prev"),
305 auto find_or_copy = [](
const std::string&
path,
const std::string& addon_id,
const std::string&
type) {
307 =
_(
"This file is outside Wesnoth’s data dirs. Do you wish to copy it into your add-on?");
310 if(optional_path.has_value()) {
311 return optional_path.value();
317 return output_path.filename().string();
319 return std::string();
324 if((id_stem ==
"unit_image")
325 || (id_stem ==
"portrait_image")
326 || (id_stem ==
"small_profile_image")
327 || (id_stem ==
"attack_image"))
329 find_widget<text_box>(
"path_"+id_stem).set_value(find_or_copy(dlg.
path(),
addon_id_,
"images"));
338 type_select->set_ok_label(
"Load");
340 if (!type_select->show() && !type_select->is_selected()) {
344 const auto&
type = all_type_list[type_select->get_selected_index()];
346 find_widget<text_box>(
"id_box").set_value(
type->id());
347 find_widget<text_box>(
"name_box").set_value(
type->type_name().base_str());
348 find_widget<spinner>(
"level_box").set_value(
type->level());
349 find_widget<slider>(
"cost_slider").set_value(
type->cost());
350 find_widget<text_box>(
"adv_box").set_value(
utils::join(
type->advances_to()));
351 find_widget<slider>(
"hp_slider").set_value(
type->hitpoints());
352 find_widget<slider>(
"xp_slider").set_value(
type->experience_needed());
353 find_widget<slider>(
"move_slider").set_value(
type->movement());
354 find_widget<scroll_text>(
"desc_box").set_value(
type->unit_description().base_str());
355 find_widget<text_box>(
"path_unit_image").set_value(
type->image());
356 find_widget<text_box>(
"path_portrait_image").set_value(
type->big_profile());
358 for (
const auto& gender :
type->genders())
360 if (gender == unit_race::GENDER::MALE) {
361 find_widget<toggle_button>(
"gender_male").set_value(
true);
364 if (gender == unit_race::GENDER::FEMALE) {
365 find_widget<toggle_button>(
"gender_female").set_value(
true);
370 find_widget<menu_button>(
"race_list"),
375 find_widget<menu_button>(
"alignment_list"),
383 for (
auto special :
type->abilities()) {
384 const std::string
id = special->cfg()[
"unique_id"].str(special->id());
386 enabled_abilities[
i] =
true;
390 find_widget<multimenu_button>(
"abilities_list").select_options(enabled_abilities);
392 find_widget<text_box>(
"path_small_profile_image").set_value(
type->small_profile());
395 find_widget<menu_button>(
"movetype_list"),
397 type->movement_type_id());
400 type->movement_type().write(
cfg,
false);
407 if (!
type->get_cfg().has_child(
"resistance")) {
411 for (
const auto& [key,
_] :
type->get_cfg().mandatory_child(
"resistance").attribute_range()) {
419 if (
type->get_cfg().has_child(
"defense")) {
420 for (
const auto& [key,
_] :
type->get_cfg().mandatory_child(
"defense").attribute_range()) {
427 if (
type->get_cfg().has_child(
"movement_costs")) {
428 for (
const auto& [key,
_] :
type->get_cfg().mandatory_child(
"movement_costs").attribute_range()) {
441 find_widget<menu_button>(
"usage_list"),
448 for(
const auto& atk :
type->attacks())
451 attack[
"name"] = atk.id();
452 attack[
"description"] = atk.name().base_str();
453 attack[
"icon"] = atk.icon();
454 attack[
"range"] = atk.range();
455 attack[
"damage"] = atk.damage();
456 attack[
"number"] = atk.num_attacks();
457 attack[
"type"] = atk.type();
462 for (
auto special : atk.specials()) {
463 const std::string
id = special->cfg()[
"unique_id"].str(special->id());
465 enabled_specials[
i] =
true;
470 attacks_.push_back(std::make_pair(enabled_specials, attack));
473 if (!
type->attacks().empty()) {
490 std::string current_textdomain =
"wesnoth-" +
addon_id_;
493 utype[
"id"] = find_widget<text_box>(
"id_box").get_value();
494 utype[
"name"] =
t_string(find_widget<text_box>(
"name_box").get_value(), current_textdomain);
495 utype[
"image"] = find_widget<text_box>(
"path_unit_image").get_value();
496 utype[
"profile"] = find_widget<text_box>(
"path_portrait_image").get_value();
497 utype[
"level"] = find_widget<spinner>(
"level_box").get_value();
498 utype[
"advances_to"] = find_widget<text_box>(
"adv_box").get_value();
499 utype[
"hitpoints"] = find_widget<slider>(
"hp_slider").get_value();
500 utype[
"experience"] = find_widget<slider>(
"xp_slider").get_value();
501 utype[
"cost"] = find_widget<slider>(
"cost_slider").get_value();
502 utype[
"movement"] = find_widget<slider>(
"move_slider").get_value();
503 utype[
"description"] =
t_string(find_widget<scroll_text>(
"desc_box").get_value(), current_textdomain);
504 utype[
"race"] = find_widget<menu_button>(
"race_list").get_value_string();
508 if (find_widget<toggle_button>(
"gender_male").get_value()) {
509 if (find_widget<toggle_button>(
"gender_female").get_value()) {
510 utype[
"gender"] =
"male,female";
512 utype[
"gender"] =
"male";
515 if (find_widget<toggle_button>(
"gender_female").get_value()) {
516 utype[
"gender"] =
"female";
522 utype[
"small_profile"] = find_widget<text_box>(
"path_small_profile_image").get_value();
523 utype[
"movement_type"] = find_widget<menu_button>(
"movetype_list").get_value_string();
524 utype[
"usage"] = find_widget<menu_button>(
"usage_list").get_value_string();
559 std::vector<std::string> selected_abilities;
560 const auto& abilities_states = find_widget<multimenu_button>(
"abilities_list").get_toggle_states();
561 if (abilities_states.any()) {
562 for (
size_t i = 0;
i < abilities_states.size();
i++) {
563 if (abilities_states[
i]) {
568 if (!selected_abilities.empty()) {
569 utype[
"abilities_list"] =
utils::join(selected_abilities);
576 std::vector<std::string> selected_specials;
577 for (
size_t i = 0;
i < special_bits.size();
i++) {
578 if (special_bits[
i]) {
582 if (!selected_specials.empty()) {
583 atk[
"specials_list"] =
utils::join(selected_specials);
589 find_widget<slider>(
"resistances_slider")
591 100 -
resistances_[find_widget<menu_button>(
"resistances_list").get_value_string()].to_int());
593 find_widget<slider>(
"resistances_slider")
594 .set_active(
res_toggles_[find_widget<menu_button>(
"resistances_list").get_value()]);
596 find_widget<toggle_button>(
"resistances_checkbox")
597 .set_value(
res_toggles_[find_widget<menu_button>(
"resistances_list").get_value()]);
601 resistances_[find_widget<menu_button>(
"resistances_list").get_value_string()]
602 = 100 - find_widget<slider>(
"resistances_slider").get_value();
606 bool toggle = find_widget<toggle_button>(
"resistances_checkbox").get_value();
607 res_toggles_[find_widget<menu_button>(
"resistances_list").get_value()] = toggle;
608 find_widget<slider>(
"resistances_slider").set_active(toggle);
612 find_widget<slider>(
"defense_slider")
614 100 -
defenses_[find_widget<menu_button>(
"defense_list").get_value_string()].to_int());
616 find_widget<slider>(
"defense_slider")
617 .set_active(
def_toggles_[find_widget<menu_button>(
"defense_list").get_value()]);
619 find_widget<toggle_button>(
"defense_checkbox")
620 .set_value(
def_toggles_[find_widget<menu_button>(
"defense_list").get_value()]);
624 defenses_[find_widget<menu_button>(
"defense_list").get_value_string()]
625 = 100 - find_widget<slider>(
"defense_slider").get_value();
629 bool toggle = find_widget<toggle_button>(
"defense_checkbox").get_value();
630 def_toggles_[find_widget<menu_button>(
"defense_list").get_value()] = toggle;
631 find_widget<slider>(
"defense_slider").set_active(toggle);
635 find_widget<slider>(
"movement_costs_slider")
637 movement_[find_widget<menu_button>(
"movement_costs_list").get_value_string()].to_int());
639 find_widget<slider>(
"movement_costs_slider")
640 .set_active(
move_toggles_[find_widget<menu_button>(
"movement_costs_list").get_value()]);
642 find_widget<toggle_button>(
"movement_costs_checkbox")
643 .set_value(
move_toggles_[find_widget<menu_button>(
"movement_costs_list").get_value()]);
647 movement_[find_widget<menu_button>(
"movement_costs_list").get_value_string()]
648 = find_widget<slider>(
"movement_costs_slider").get_value();
652 bool toggle = find_widget<toggle_button>(
"movement_costs_checkbox").get_value();
653 move_toggles_[find_widget<menu_button>(
"movement_costs_list").get_value()] = toggle;
654 find_widget<slider>(
"movement_costs_slider").set_active(toggle);
659 std::string current_textdomain =
"wesnoth-"+
addon_id_;
667 attack[
"name"] = find_widget<text_box>(
"atk_id_box").get_value();
668 attack[
"description"] =
t_string(find_widget<text_box>(
"atk_name_box").get_value(), current_textdomain);
669 attack[
"icon"] = find_widget<text_box>(
"path_attack_image").get_value();
670 attack[
"type"] = find_widget<combobox>(
"attack_type_list").get_value();
671 attack[
"damage"] = find_widget<slider>(
"dmg_box").get_value();
672 attack[
"number"] = find_widget<slider>(
"dmg_num_box").get_value();
673 attack[
"range"] = find_widget<combobox>(
"range_list").get_value();
676 find_widget<multimenu_button>(
"weapon_specials_list").get_toggle_states(),
689 find_widget<text_box>(
"atk_id_box").set_value(attack[
"name"]);
690 find_widget<text_box>(
"atk_name_box").set_value(attack[
"description"]);
691 find_widget<text_box>(
"path_attack_image").set_value(attack[
"icon"]);
693 find_widget<slider>(
"dmg_box").set_value(attack[
"damage"].to_int());
694 find_widget<slider>(
"dmg_num_box").set_value(attack[
"number"].to_int());
695 find_widget<combobox>(
"range_list").set_value(attack[
"range"]);
700 find_widget<multimenu_button>(
"weapon_specials_list")
710 std::vector<config> atk_name_list;
711 for(
const auto& atk_data :
attacks_) {
712 atk_name_list.emplace_back(
"label", atk_data.second[
"name"]);
714 menu_button& atk_list = find_widget<menu_button>(
"atk_list");
721 find_widget<label>(
"atk_number").set_label(new_index_str);
726 std::string current_textdomain =
"wesnoth-"+
addon_id_;
729 attack[
"name"] = find_widget<text_box>(
"atk_id_box").get_value();
730 attack[
"description"] =
t_string(find_widget<text_box>(
"atk_name_box").get_value(), current_textdomain);
731 attack[
"icon"] = find_widget<text_box>(
"path_attack_image").get_value();
732 attack[
"type"] = find_widget<combobox>(
"attack_type_list").get_value();
733 attack[
"damage"] = find_widget<slider>(
"dmg_box").get_value();
734 attack[
"number"] = find_widget<slider>(
"dmg_num_box").get_value();
735 attack[
"range"] = find_widget<combobox>(
"range_list").get_value();
741 , std::make_pair(find_widget<multimenu_button>(
"weapon_specials_list").get_toggle_states(), attack));
754 find_widget<button>(
"atk_delete").set_active(
false);
800 .mandatory_child(
"units")
801 .child_range(
"movetype")) {
802 if (
movetype[
"name"] == find_widget<menu_button>(
"movetype_list").get_value_string()) {
820 std::stringstream wml_stream;
823 std::string current_textdomain =
"wesnoth-" +
addon_id_;
826 <<
"#textdomain " << current_textdomain <<
"\n"
828 <<
"# This file was generated using the scenario editor.\n"
842 mvt_cfg[key] = value;
854 def_cfg[key] = value;
866 res_cfg[key] = value;
874 find_widget<scroll_text>(
"wml_view").set_label(
generated_wml);
878 std::string rel_path = find_widget<text_box>(
"path_"+id_stem).get_value();
881 if (rel_path.find(
"~") != std::string::npos) {
882 rel_path = rel_path.substr(0, rel_path.find(
"~"));
885 int scale_size = 200;
886 if (rel_path.size() > 0) {
888 float aspect_ratio =
static_cast<float>(img_size.x)/img_size.y;
889 if(img_size.x > scale_size) {
890 rel_path.append(
"~SCALE(" + std::to_string(scale_size) +
"," + std::to_string(scale_size*aspect_ratio) +
")");
891 }
else if (img_size.y > scale_size) {
892 rel_path.append(
"~SCALE(" + std::to_string(scale_size/aspect_ratio) +
"," + std::to_string(scale_size) +
")");
896 if (id_stem ==
"portrait_image") {
898 find_widget<image>(
"unit_image").set_label(rel_path);
900 find_widget<image>(id_stem).set_label(rel_path);
909 if (!(std::isalnum(
c) ||
c ==
'_' ||
c ==
' ')) {
918 std::string
id = find_widget<text_box>(
"id_box").get_value();
919 std::string name = find_widget<text_box>(
"name_box").get_value();
921 find_widget<button>(
"ok").set_active(!
id.empty() && !name.empty() &&
check_id(
id));
928 =
_(
"Unsaved changes will be lost. Do you want to leave?");
939 boost::algorithm::replace_all(
unit_name,
" ",
"_");
955 const SDL_Keycode key,
960 const SDL_Keycode modifier_key = KMOD_GUI;
964 const SDL_Keycode modifier_key = KMOD_CTRL;
970 if (modifier & modifier_key) {
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.
config & add_child(std::string_view key)
std::size_t attribute_count() const
Count the number of non-blank attributes.
const_attr_itors attribute_range() const
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.
A class grating read only view to a vector of config objects, viewed as one config with all children ...
const config & mandatory_child(std::string_view key) const
void set_values(const std::vector<::config > &values, unsigned selected=0)
Dialog that allows user to create custom unit types.
unsigned int selected_attack_
0 means there are no attacks.
void write()
Write the cfg file.
void quit_confirmation()
Quit confirmation.
void set_selected_from_string(menu_button &list, std::vector< config > values, const std::string &item)
std::vector< config > defense_list_
boost::dynamic_bitset res_toggles_
Used to control checkboxes for various resistances, defences, etc.
const game_config_view & game_config_
std::vector< std::pair< boost::dynamic_bitset<>, config > > attacks_
std::vector< config > abilities_list_
void store_attack()
Callbacks for attack page.
std::vector< config > movetype_list_
std::vector< config > race_list_
void update_defenses()
Callbacks for defense list.
std::string generated_wml
Generated WML.
void save_unit_type()
Save Unit Type data to cfg.
std::vector< config > align_list_
void button_state_change()
Callback to enable/disable OK button if ID/Name is invalid.
void signal_handler_sdl_key_down(const event::ui_event, bool &handled, const SDL_Keycode key, SDL_Keymod modifier)
editor_edit_unit(const game_config_view &game_config, const std::string &addon_id)
void select_file(const std::string &default_dir, const std::string &id_stem)
Callback for file select button.
boost::dynamic_bitset move_toggles_
void update_movement_costs()
Callbacks for movement list.
std::vector< config > resistances_list_
void enable_defense_slider()
void on_page_select()
Callback when an tab item in the "page" listbox is selected.
void update_resistances()
Callback for resistance list.
bool check_id(const std::string &id)
Utility method to check if ID contains any invalid characters.
const std::string & addon_id_
void store_movement_costs()
void enable_movement_slider()
std::vector< config > specials_list_
void enable_resistances_slider()
boost::dynamic_bitset def_toggles_
void load_unit_type()
Load Unit Type data from cfg.
virtual void pre_show() override
Actions to be taken before showing the window.
void update_wml_view()
Update wml preview.
std::vector< config > usage_type_list_
void load_movetype()
Callback for loading movetype data in UI.
void update_image(const std::string &id_stem)
Callback for image update.
file_dialog & set_ok_label(const std::string &value)
Sets the OK button label.
file_dialog & set_path(const std::string &value)
Sets the initial file selection.
file_dialog & set_title(const std::string &value)
Sets the current dialog title text.
file_dialog & set_read_only(bool value)
Whether to provide user interface elements for manipulating existing objects.
std::string path() const
Gets the current file selection.
Main class to show messages to the user.
@ yes_no_buttons
Shows a yes and no button.
Abstract base class for all modal dialogs.
bool show(const unsigned auto_close_time=0)
Shows the window.
static std::unique_ptr< units_dialog > build_create_dialog(const std::vector< const unit_type * > &types_list)
A container widget that shows one of its pages of widgets depending on which tab the user clicked.
unsigned get_active_tab_index()
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
void invalidate_layout()
Updates the size of the window.
static const std::string & type()
Static type getter that does not rely on the widget being constructed.
Generic locator abstracting the location of an image.
The basic "size" of the unit - flying, small land, large land, etc.
const std::map< std::string, config > & abilities() const
const movement_type_map & movement_types() const
const race_map & races() const
const std::vector< const unit_type * > types_list() const
const std::map< std::string, config > & specials() const
Declarations for File-IO.
static std::string _(const char *str)
const std::string wml_extension
void copy_file(const std::string &src, const std::string &dest)
Read a file and then writes it back out.
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_current_editor_dir(const std::string &addon_id)
utils::optional< std::string > to_asset_path(const std::string &path, const std::string &addon_id, const std::string &asset_type)
Helper function to convert absolute path to wesnoth relative path.
Game configuration data as global variables.
REGISTER_DIALOG(editor_edit_unit)
ui_event
The event sent to the dispatcher.
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
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.
@ CANCEL
Dialog was closed with the CANCEL button.
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
static config unit_name(const unit *u)
An exception object used when an IO error occurs.
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
unit_type_data unit_types