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 race_list_.emplace_back(
"label", race.id(),
"icon", race.get_icon_path_stem() +
"_30.png");
103 button&
load = find_widget<button>(
"load_unit_type");
115 find_widget<button>(
"browse_unit_image"),
118 find_widget<button>(
"preview_unit_image"),
121 find_widget<button>(
"browse_portrait_image"),
124 find_widget<button>(
"preview_portrait_image"),
128 find_widget<text_box>(
"name_box"),
131 find_widget<text_box>(
"id_box"),
138 menu_button& movetypes = find_widget<menu_button>(
"movetype_list");
147 menu_button& defenses = find_widget<menu_button>(
"defense_list");
156 menu_button& movement_costs = find_widget<menu_button>(
"movement_costs_list");
164 menu_button& resistances = find_widget<menu_button>(
"resistances_list");
171 resistances_list_.emplace_back(
"label", key,
"icon",
"icons/profiles/" + key +
".png");
179 menu_button& usage_types = find_widget<menu_button>(
"usage_list");
187 multimenu_button& abilities = find_widget<multimenu_button>(
"abilities_list");
191 find_widget<button>(
"browse_small_profile_image"),
194 find_widget<button>(
"preview_small_profile_image"),
197 find_widget<button>(
"load_movetype"),
200 find_widget<slider>(
"resistances_slider"),
203 find_widget<menu_button>(
"resistances_list"),
206 find_widget<toggle_button>(
"resistances_checkbox"),
210 find_widget<slider>(
"defense_slider"),
213 find_widget<menu_button>(
"defense_list"),
216 find_widget<toggle_button>(
"defense_checkbox"),
220 find_widget<slider>(
"movement_costs_slider"),
223 find_widget<menu_button>(
"movement_costs_list"),
226 find_widget<toggle_button>(
"movement_costs_checkbox"),
245 multimenu_button& specials = find_widget<multimenu_button>(
"weapon_specials_list");
248 combobox& attack_types = find_widget<combobox>(
"attack_type_list");
255 find_widget<button>(
"browse_attack_image"),
258 find_widget<button>(
"preview_attack_image"),
261 find_widget<menu_button>(
"atk_list"),
264 find_widget<button>(
"atk_new"),
267 find_widget<button>(
"atk_delete"),
270 find_widget<button>(
"atk_next"),
273 find_widget<button>(
"atk_prev"),
304 auto find_or_copy = [](
const std::string&
path,
const std::string& addon_id,
const std::string&
type) {
306 =
_(
"This file is outside Wesnoth’s data dirs. Do you wish to copy it into your add-on?");
309 if(optional_path.has_value()) {
310 return optional_path.value();
316 return output_path.filename().string();
318 return std::string();
323 if((id_stem ==
"unit_image")
324 || (id_stem ==
"portrait_image")
325 || (id_stem ==
"small_profile_image")
326 || (id_stem ==
"attack_image"))
328 find_widget<text_box>(
"path_"+id_stem).set_value(find_or_copy(dlg.
path(),
addon_id_,
"images"));
337 type_select->set_ok_label(
"Load");
339 if (!type_select->show() && !type_select->is_selected()) {
343 const auto&
type = all_type_list[type_select->get_selected_index()];
345 find_widget<text_box>(
"id_box").set_value(
type->id());
346 find_widget<text_box>(
"name_box").set_value(
type->type_name().base_str());
347 find_widget<spinner>(
"level_box").set_value(
type->level());
348 find_widget<slider>(
"cost_slider").set_value(
type->cost());
349 find_widget<text_box>(
"adv_box").set_value(
utils::join(
type->advances_to()));
350 find_widget<slider>(
"hp_slider").set_value(
type->hitpoints());
351 find_widget<slider>(
"xp_slider").set_value(
type->experience_needed());
352 find_widget<slider>(
"move_slider").set_value(
type->movement());
353 find_widget<scroll_text>(
"desc_box").set_value(
type->unit_description().base_str());
354 find_widget<text_box>(
"path_unit_image").set_value(
type->image());
355 find_widget<text_box>(
"path_portrait_image").set_value(
type->big_profile());
357 for (
const auto& gender :
type->genders())
359 if (gender == unit_race::GENDER::MALE) {
360 find_widget<toggle_button>(
"gender_male").set_value(
true);
363 if (gender == unit_race::GENDER::FEMALE) {
364 find_widget<toggle_button>(
"gender_female").set_value(
true);
369 find_widget<menu_button>(
"race_list"),
374 find_widget<menu_button>(
"alignment_list"),
382 for (
auto special :
type->abilities()) {
383 const std::string
id = special->cfg()[
"unique_id"].str(special->id());
385 enabled_abilities[
i] =
true;
389 find_widget<multimenu_button>(
"abilities_list").select_options(enabled_abilities);
391 find_widget<text_box>(
"path_small_profile_image").set_value(
type->small_profile());
394 find_widget<menu_button>(
"movetype_list"),
396 type->movement_type_id());
399 type->movement_type().write(
cfg,
false);
406 if (!
type->get_cfg().has_child(
"resistance")) {
410 for (
const auto& [key,
_] :
type->get_cfg().mandatory_child(
"resistance").attribute_range()) {
418 if (
type->get_cfg().has_child(
"defense")) {
419 for (
const auto& [key,
_] :
type->get_cfg().mandatory_child(
"defense").attribute_range()) {
426 if (
type->get_cfg().has_child(
"movement_costs")) {
427 for (
const auto& [key,
_] :
type->get_cfg().mandatory_child(
"movement_costs").attribute_range()) {
440 find_widget<menu_button>(
"usage_list"),
447 for(
const auto& atk :
type->attacks())
450 attack[
"name"] = atk.id();
451 attack[
"description"] = atk.name().base_str();
452 attack[
"icon"] = atk.icon();
453 attack[
"range"] = atk.range();
454 attack[
"damage"] = atk.damage();
455 attack[
"number"] = atk.num_attacks();
456 attack[
"type"] = atk.type();
461 for (
auto special : atk.specials()) {
462 const std::string
id = special->cfg()[
"unique_id"].str(special->id());
464 enabled_specials[
i] =
true;
469 attacks_.push_back(std::make_pair(enabled_specials, attack));
472 if (!
type->attacks().empty()) {
489 std::string current_textdomain =
"wesnoth-" +
addon_id_;
492 utype[
"id"] = find_widget<text_box>(
"id_box").get_value();
493 utype[
"name"] =
t_string(find_widget<text_box>(
"name_box").get_value(), current_textdomain);
494 utype[
"image"] = find_widget<text_box>(
"path_unit_image").get_value();
495 utype[
"profile"] = find_widget<text_box>(
"path_portrait_image").get_value();
496 utype[
"level"] = find_widget<spinner>(
"level_box").get_value();
497 utype[
"advances_to"] = find_widget<text_box>(
"adv_box").get_value();
498 utype[
"hitpoints"] = find_widget<slider>(
"hp_slider").get_value();
499 utype[
"experience"] = find_widget<slider>(
"xp_slider").get_value();
500 utype[
"cost"] = find_widget<slider>(
"cost_slider").get_value();
501 utype[
"movement"] = find_widget<slider>(
"move_slider").get_value();
502 utype[
"description"] =
t_string(find_widget<scroll_text>(
"desc_box").get_value(), current_textdomain);
503 utype[
"race"] = find_widget<menu_button>(
"race_list").get_value_string();
507 if (find_widget<toggle_button>(
"gender_male").get_value()) {
508 if (find_widget<toggle_button>(
"gender_female").get_value()) {
509 utype[
"gender"] =
"male,female";
511 utype[
"gender"] =
"male";
514 if (find_widget<toggle_button>(
"gender_female").get_value()) {
515 utype[
"gender"] =
"female";
521 utype[
"small_profile"] = find_widget<text_box>(
"path_small_profile_image").get_value();
522 utype[
"movement_type"] = find_widget<menu_button>(
"movetype_list").get_value_string();
523 utype[
"usage"] = find_widget<menu_button>(
"usage_list").get_value_string();
558 std::vector<std::string> selected_abilities;
559 const auto& abilities_states = find_widget<multimenu_button>(
"abilities_list").get_toggle_states();
560 if (abilities_states.any()) {
561 for (
size_t i = 0;
i < abilities_states.size();
i++) {
562 if (abilities_states[
i]) {
567 if (!selected_abilities.empty()) {
568 utype[
"abilities_list"] =
utils::join(selected_abilities);
575 std::vector<std::string> selected_specials;
576 for (
size_t i = 0;
i < special_bits.size();
i++) {
577 if (special_bits[
i]) {
581 if (!selected_specials.empty()) {
582 atk[
"specials_list"] =
utils::join(selected_specials);
588 find_widget<slider>(
"resistances_slider")
590 100 -
resistances_[find_widget<menu_button>(
"resistances_list").get_value_string()].to_int());
592 find_widget<slider>(
"resistances_slider")
593 .set_active(
res_toggles_[find_widget<menu_button>(
"resistances_list").get_value()]);
595 find_widget<toggle_button>(
"resistances_checkbox")
596 .set_value(
res_toggles_[find_widget<menu_button>(
"resistances_list").get_value()]);
600 resistances_[find_widget<menu_button>(
"resistances_list").get_value_string()]
601 = 100 - find_widget<slider>(
"resistances_slider").get_value();
605 bool toggle = find_widget<toggle_button>(
"resistances_checkbox").get_value();
606 res_toggles_[find_widget<menu_button>(
"resistances_list").get_value()] = toggle;
607 find_widget<slider>(
"resistances_slider").set_active(toggle);
611 find_widget<slider>(
"defense_slider")
613 100 -
defenses_[find_widget<menu_button>(
"defense_list").get_value_string()].to_int());
615 find_widget<slider>(
"defense_slider")
616 .set_active(
def_toggles_[find_widget<menu_button>(
"defense_list").get_value()]);
618 find_widget<toggle_button>(
"defense_checkbox")
619 .set_value(
def_toggles_[find_widget<menu_button>(
"defense_list").get_value()]);
623 defenses_[find_widget<menu_button>(
"defense_list").get_value_string()]
624 = 100 - find_widget<slider>(
"defense_slider").get_value();
628 bool toggle = find_widget<toggle_button>(
"defense_checkbox").get_value();
629 def_toggles_[find_widget<menu_button>(
"defense_list").get_value()] = toggle;
630 find_widget<slider>(
"defense_slider").set_active(toggle);
634 find_widget<slider>(
"movement_costs_slider")
636 movement_[find_widget<menu_button>(
"movement_costs_list").get_value_string()].to_int());
638 find_widget<slider>(
"movement_costs_slider")
639 .set_active(
move_toggles_[find_widget<menu_button>(
"movement_costs_list").get_value()]);
641 find_widget<toggle_button>(
"movement_costs_checkbox")
642 .set_value(
move_toggles_[find_widget<menu_button>(
"movement_costs_list").get_value()]);
646 movement_[find_widget<menu_button>(
"movement_costs_list").get_value_string()]
647 = find_widget<slider>(
"movement_costs_slider").get_value();
651 bool toggle = find_widget<toggle_button>(
"movement_costs_checkbox").get_value();
652 move_toggles_[find_widget<menu_button>(
"movement_costs_list").get_value()] = toggle;
653 find_widget<slider>(
"movement_costs_slider").set_active(toggle);
658 std::string current_textdomain =
"wesnoth-"+
addon_id_;
666 attack[
"name"] = find_widget<text_box>(
"atk_id_box").get_value();
667 attack[
"description"] =
t_string(find_widget<text_box>(
"atk_name_box").get_value(), current_textdomain);
668 attack[
"icon"] = find_widget<text_box>(
"path_attack_image").get_value();
669 attack[
"type"] = find_widget<combobox>(
"attack_type_list").get_value();
670 attack[
"damage"] = find_widget<slider>(
"dmg_box").get_value();
671 attack[
"number"] = find_widget<slider>(
"dmg_num_box").get_value();
672 attack[
"range"] = find_widget<combobox>(
"range_list").get_value();
675 find_widget<multimenu_button>(
"weapon_specials_list").get_toggle_states(),
688 find_widget<text_box>(
"atk_id_box").set_value(attack[
"name"]);
689 find_widget<text_box>(
"atk_name_box").set_value(attack[
"description"]);
690 find_widget<text_box>(
"path_attack_image").set_value(attack[
"icon"]);
692 find_widget<slider>(
"dmg_box").set_value(attack[
"damage"].to_int());
693 find_widget<slider>(
"dmg_num_box").set_value(attack[
"number"].to_int());
694 find_widget<combobox>(
"range_list").set_value(attack[
"range"]);
699 find_widget<multimenu_button>(
"weapon_specials_list")
709 std::vector<config> atk_name_list;
710 for(
const auto& atk_data :
attacks_) {
711 atk_name_list.emplace_back(
"label", atk_data.second[
"name"]);
713 menu_button& atk_list = find_widget<menu_button>(
"atk_list");
720 find_widget<label>(
"atk_number").set_label(new_index_str);
725 std::string current_textdomain =
"wesnoth-"+
addon_id_;
728 attack[
"name"] = find_widget<text_box>(
"atk_id_box").get_value();
729 attack[
"description"] =
t_string(find_widget<text_box>(
"atk_name_box").get_value(), current_textdomain);
730 attack[
"icon"] = find_widget<text_box>(
"path_attack_image").get_value();
731 attack[
"type"] = find_widget<combobox>(
"attack_type_list").get_value();
732 attack[
"damage"] = find_widget<slider>(
"dmg_box").get_value();
733 attack[
"number"] = find_widget<slider>(
"dmg_num_box").get_value();
734 attack[
"range"] = find_widget<combobox>(
"range_list").get_value();
740 , std::make_pair(find_widget<multimenu_button>(
"weapon_specials_list").get_toggle_states(), attack));
753 find_widget<button>(
"atk_delete").set_active(
false);
799 .mandatory_child(
"units")
800 .child_range(
"movetype")) {
801 if (
movetype[
"name"] == find_widget<menu_button>(
"movetype_list").get_value_string()) {
819 std::stringstream wml_stream;
822 std::string current_textdomain =
"wesnoth-" +
addon_id_;
825 <<
"#textdomain " << current_textdomain <<
"\n"
827 <<
"# This file was generated using the scenario editor.\n"
841 mvt_cfg[key] = value;
853 def_cfg[key] = value;
865 res_cfg[key] = value;
873 find_widget<scroll_text>(
"wml_view").set_label(
generated_wml);
877 std::string rel_path = find_widget<text_box>(
"path_"+id_stem).get_value();
880 if (rel_path.find(
"~") != std::string::npos) {
881 rel_path = rel_path.substr(0, rel_path.find(
"~"));
884 int scale_size = 200;
885 if (rel_path.size() > 0) {
887 float aspect_ratio =
static_cast<float>(img_size.x)/img_size.y;
888 if(img_size.x > scale_size) {
889 rel_path.append(
"~SCALE(" + std::to_string(scale_size) +
"," + std::to_string(scale_size*aspect_ratio) +
")");
890 }
else if (img_size.y > scale_size) {
891 rel_path.append(
"~SCALE(" + std::to_string(scale_size/aspect_ratio) +
"," + std::to_string(scale_size) +
")");
895 if (id_stem ==
"portrait_image") {
897 find_widget<image>(
"unit_image").set_label(rel_path);
899 find_widget<image>(id_stem).set_label(rel_path);
908 if (!(std::isalnum(
c) ||
c ==
'_' ||
c ==
' ')) {
917 std::string
id = find_widget<text_box>(
"id_box").get_value();
918 std::string name = find_widget<text_box>(
"name_box").get_value();
920 find_widget<button>(
"ok").set_active(!
id.empty() && !name.empty() &&
check_id(
id));
927 =
_(
"Unsaved changes will be lost. Do you want to leave?");
938 boost::algorithm::replace_all(
unit_name,
" ",
"_");
954 const SDL_Keycode key,
959 const SDL_Keycode modifier_key = KMOD_GUI;
963 const SDL_Keycode modifier_key = KMOD_CTRL;
969 if (modifier & modifier_key) {
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.
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