16 #define GETTEXT_DOMAIN "wesnoth-lib"
21 #include "addon/manager.hpp"
56 struct filter_transform
58 explicit filter_transform(
const std::vector<std::string>& filtertext) :
filtertext_(filtertext) {}
59 bool operator()(
const config& cfg)
const
73 for(
const auto& [
_, value] : child.attribute_range()) {
89 std::string make_display_dependencies(
90 const std::string& addon_id,
99 for(
const auto& dep_id : deps) {
103 addons_list::const_iterator ali =
addons_list.find(dep_id);
104 addons_tracking_list::const_iterator tli = addon_states.find(dep_id);
112 if(tli == addon_states.end()) {
115 depstate = tli->second;
128 std::string langcode_to_string(
const std::string& lcode)
132 if(ld.localename == lcode || ld.localename.substr(0, 2) == lcode) {
170 {
N_(
"addons_order^Name ($order)"),
"name", 0,
173 {
N_(
"addons_order^Author ($order)"),
"author", 1,
176 {
N_(
"addons_order^Size ($order)"),
"size", 2,
179 {
N_(
"addons_order^Downloads ($order)"),
"downloads", 3,
182 {
N_(
"addons_order^Type ($order)"),
"type", 4,
185 {
N_(
"addons_order^Last updated ($datelike_order)"),
"last_updated", -1,
188 {
N_(
"addons_order^First uploaded ($datelike_order)"),
"first_uploaded", -1,
205 const std::vector<addon_tag> tag_filter_types_{
206 {
"cooperative",
N_(
"addon_tag^Cooperative"),
208 N_(
"addon_tag^All human players are on the same team, versus the AI")},
209 {
"cosmetic",
N_(
"addon_tag^Cosmetic"),
211 N_(
"addon_tag^These make the game look different, without changing gameplay")},
212 {
"difficulty",
N_(
"addon_tag^Difficulty"),
214 N_(
"addon_tag^Can make campaigns easier or harder")},
215 {
"rng",
N_(
"addon_tag^RNG"),
217 N_(
"addon_tag^Modify the randomness in the combat mechanics, or remove it entirely")},
218 {
"survival",
N_(
"addon_tag^Survival"),
220 N_(
"addon_tag^Fight against waves of enemies")},
221 {
"terraforming",
N_(
"addon_tag^Terraforming"),
223 N_(
"addon_tag^Players can change the terrain")},
234 , need_wml_cache_refresh_(false)
244 switch(state.
state) {
247 ?
_(
"addon_state^Not installed")
248 :
_(
"addon_state^Published, not installed");
252 ?
_(
"addon_state^Installed")
253 :
_(
"addon_state^Published");
257 ?
_(
"addon_state^Installed, not tracking local version")
260 :
_(
"addon_state^Published, not tracking local version");
264 ?
_(
"addon_state^Installed ($local_version|), upgradable")
265 :
_(
"addon_state^Published ($local_version| installed), upgradable");
271 ?
_(
"addon_state^Installed ($local_version|), outdated on server")
272 :
_(
"addon_state^Published ($local_version| installed), outdated on server");
278 ?
_(
"addon_state^Installed, not ready to publish")
279 :
_(
"addon_state^Ready to publish");
283 ?
_(
"addon_state^Installed, broken")
284 :
_(
"addon_state^Published, broken");
287 s =
_(
"addon_state^Unknown");
297 stacked_widget& addr_info = find_widget<stacked_widget>(
"server_conn_info");
309 auto addr_box =
dynamic_cast<styled_widget*
>(addr_visible->
find(
"server_addr",
false));
319 addr_box->set_label(full_id.str());
326 addon_list& list = find_widget<addon_list>(
"addons");
328 text_box& filter = find_widget<text_box>(
"filter");
332 this, std::placeholders::_1));
334 this, std::placeholders::_1));
336 this, std::placeholders::_1));
339 this, std::placeholders::_1));
341 this, std::placeholders::_1));
348 menu_button& status_filter = find_widget<menu_button>(
"install_status_filter");
350 std::vector<config> status_filter_entries;
355 status_filter.
set_values(status_filter_entries);
361 auto& tag_filter = find_widget<multimenu_button>(
"tag_filter");
363 std::vector<config> tag_filter_entries;
364 for(
const auto&
f : tag_filter_types_) {
366 if(!
f.tooltip.empty()) {
371 tag_filter.set_values(tag_filter_entries);
376 multimenu_button& type_filter = find_widget<multimenu_button>(
"type_filter");
378 std::vector<config> type_filter_entries;
390 std::set<std::string> languages_available;
392 for (
const auto&
b : a.second.
locales) {
393 languages_available.insert(
b);
396 std::set<std::string> language_strings_available;
397 for (
const auto&
i: languages_available) {
401 if (std::string lang_code_string = langcode_to_string(
i); !lang_code_string.empty()) {
402 language_strings_available.insert(lang_code_string);
405 for (
auto&
i: language_strings_available) {
409 multimenu_button& language_filter = find_widget<multimenu_button>(
"language_filter");
410 std::vector<config> language_filter_entries;
412 language_filter_entries.emplace_back(
"label",
f.second,
"checkbox",
false);
415 language_filter.
set_values(language_filter_entries);
421 menu_button& order_dropdown = find_widget<menu_button>(
"order_dropdown");
423 std::vector<config> order_dropdown_entries;
427 symbols[
"order"] =
_(
"ascending");
429 symbols[
"datelike_order"] =
_(
"oldest to newest");
431 order_dropdown_entries.push_back(entry);
432 symbols[
"order"] =
_(
"descending");
434 symbols[
"datelike_order"] =
_(
"newest to oldest");
435 entry[
"label"] =
VGETTEXT(
f.label.c_str(), symbols);
436 order_dropdown_entries.push_back(entry);
439 order_dropdown.
set_values(order_dropdown_entries);
441 const std::string saved_order_name =
prefs::get().addon_manager_saved_order_name();
444 if(!saved_order_name.empty()) {
446 [&saved_order_name](
const addon_order& order) {return order.as_preference == saved_order_name;});
450 if(saved_order_direction == sort_order::type::ascending) {
451 func = order_it->sort_func_asc;
453 func = order_it->sort_func_desc;
456 find_widget<menu_button>(
"order_dropdown").set_value(
index);
457 auto& addons = find_widget<addon_list>(
"addons");
458 addons.set_addon_order(func);
459 addons.select_first_addon();
467 label& url_label = find_widget<label>(
"url");
473 find_widget<button>(
"install"),
477 find_widget<button>(
"uninstall"),
481 find_widget<button>(
"update"),
485 find_widget<button>(
"publish"),
489 find_widget<button>(
"delete"),
493 find_widget<button>(
"update_all"),
497 find_widget<button>(
"show_help"),
500 if(
stacked_widget* stk = find_widget<stacked_widget>(
"main_stack",
false,
false)) {
501 button& btn = find_widget<button>(
"details_toggle");
505 stk->select_layer(0);
508 stk->get_layer_grid(1)->find_widget<
menu_button>(
"version_filter"),
512 find_widget<menu_button>(
"version_filter"),
559 for(std::string
id : publishable_addons) {
568 pbl_cfg[
"name"] =
id;
569 pbl_cfg[
"local_only"] =
true;
582 show_transient_message(
_(
"No Add-ons Available"),
_(
"There are no add-ons available for download from this server."));
585 addon_list& list = find_widget<addon_list>(
"addons");
588 bool has_upgradable_addons =
false;
593 has_upgradable_addons =
true;
597 find_widget<button>(
"update_all").set_active(has_upgradable_addons);
607 find_widget<addon_list>(
"addons").select_addon(
id);
613 const text_box& name_filter = find_widget<const text_box>(
"filter");
614 const std::string& text = name_filter.
get_value();
617 boost::dynamic_bitset<> res;
623 const config& addon_cfg = *std::find_if(addon_cfgs.begin(), addon_cfgs.end(),
626 return cfg[
"name"] == a.first;
629 res.push_back(filter(addon_cfg));
637 const menu_button& status_filter = find_widget<const menu_button>(
"install_status_filter");
640 boost::dynamic_bitset<> res;
658 const auto& tag_filter = find_widget<const multimenu_button>(
"tag_filter");
659 const auto toggle_states = tag_filter.get_toggle_states();
660 if(toggle_states.none()) {
662 boost::dynamic_bitset<> res_flipped(
addons_.size());
666 std::vector<std::string> selected_tags;
667 for(std::size_t
i = 0;
i < tag_filter_types_.size(); ++
i) {
668 if(toggle_states[
i]) {
669 selected_tags.push_back(tag_filter_types_[
i].
id);
673 boost::dynamic_bitset<> res;
675 bool matched_tag =
false;
676 for(
const auto&
id : selected_tags) {
682 res.push_back(matched_tag);
690 const multimenu_button& type_filter = find_widget<const multimenu_button>(
"type_filter");
693 if(toggle_states.none()) {
695 boost::dynamic_bitset<> res_flipped(
addons_.size());
698 boost::dynamic_bitset<> res;
703 [&a](
const std::pair<ADDON_TYPE, std::string>& entry) {
704 return entry.first == a.second.type;
707 res.push_back(toggle_states[
index]);
715 const multimenu_button& lang_filter = find_widget<const multimenu_button>(
"language_filter");
719 if(toggle_states.none()) {
720 boost::dynamic_bitset<> res_flipped(
addons_.size());
723 boost::dynamic_bitset<> res;
728 std::vector<std::string> lang_string_vector;
729 for (
long unsigned int i = 0;
i < a.second.
locales.size();
i++) {
730 lang_string_vector.push_back(langcode_to_string(a.second.
locales[
i]));
733 for (
long unsigned int i = 0;
i < toggle_states.size();
i++) {
734 if (toggle_states[
i] ==
true) {
739 if ((contains_lang_code || contains_lang_string) ==
true)
758 auto list = find_widget<addon_list>(
"addons",
false,
false);
763 boost::dynamic_bitset<> res =
769 list->set_addon_shown(res);
774 const menu_button& order_menu = find_widget<const menu_button>(
"order_dropdown");
778 if(order == sort_order::type::ascending) {
784 find_widget<addon_list>(
"addons").set_addon_order(func);
791 menu_button& order_menu = find_widget<menu_button>(
"order_dropdown");
793 [sort_column](
const addon_order& order) {return order.column_index == static_cast<int>(sort_column);});
795 if(order == sort_order::type::descending) {
799 prefs::get().set_addon_manager_saved_order_name(order_it->as_preference);
803 template<
void(addon_manager::*fptr)(const addon_info& addon)>
807 if(
stacked_widget* stk = find_widget<stacked_widget>(
"main_stack",
false,
false)) {
808 stk->select_layer(0);
809 find_widget<button>(
"details_toggle").set_label(
_(
"Add-on Details"));
812 addon_list& addons = find_widget<addon_list>(
"addons");
815 if(addon ==
nullptr) {
820 (this->*fptr)(*addon);
829 if(
stacked_widget* stk = find_widget<stacked_widget>(
"main_stack",
false,
false)) {
832 if(addon.
id == find_widget<addon_list>(
"addons").get_selected_addon()->id) {
833 versioned_addon.
current_version = find_widget<menu_button>(
"version_filter").get_value_string();
850 _(
"The following add-on appears to have publishing or version control information stored locally, and will not be removed:")
903 std::string server_msg;
905 const std::string addon_id = addon.
id;
909 const version_info& version_to_publish = cfg[
"version"].str();
911 if(version_to_publish <=
tracking_info_[addon_id].remote_version) {
913 _(
"The remote version of this add-on is greater or equal to the version being uploaded. Do you really wish to continue?"),
922 if(cfg[
"passphrase"].empty()) {
924 if(!gui2::dialogs::addon_auth::execute(cfg)) {
929 }
else if(cfg[
"forum_auth"].to_bool()) {
940 }
else if(gui2::dialogs::addon_license_prompt::execute(server_msg)) {
942 const std::string&
msg =
_(
"The add-on was rejected by the server:") +
945 if (!extra_data.empty()) {
967 const std::string addon_id = addon.
id;
969 "Deleting '$addon|' will permanently erase its download and upload counts on the add-ons server. Do you really wish to continue?",
979 std::string server_msg;
1001 VGETTEXT(
"Do you want to uninstall '$addon|'?", symbols),
1027 if(time == std::chrono::system_clock::time_point{}) {
1034 ?
_(
"%B %d %Y, %I:%M %p")
1037 :
_(
"%B %d %Y, %H:%M");
1039 auto as_time_t = std::chrono::system_clock::to_time_t(time);
1047 if(
stacked_widget* stk = find_widget<stacked_widget>(
"main_stack",
false,
false)) {
1048 parent = stk->get_layer_grid(1);
1049 info = stk->get_layer_grid(0)->find_widget<
addon_list>(
"addons").get_selected_addon();
1051 info = find_widget<addon_list>(
"addons").get_selected_addon();
1054 if(
info ==
nullptr) {
1067 status.set_use_markup(
true);
1076 :
_(
"addon_dependencies^None"));
1078 std::string languages;
1080 for(
const auto& lc :
info->locales) {
1081 const std::string& langlabel = langcode_to_string(lc);
1082 if(!langlabel.empty()) {
1083 if(!languages.empty()) {
1086 languages += langlabel;
1092 const std::string& feedback_url =
info->feedback_url;
1101 std::vector<config> version_filter_entries;
1117 for(
const auto&
f :
info->versions) {
1118 version_filter_entries.emplace_back(
"label",
f.str());
1128 version_filter_entries.emplace_back(
"label",
info->current_version.str());
1131 version_filter.set_values(version_filter_entries);
1132 version_filter.set_active(version_filter_entries.size() > 1);
1138 if(
stacked_widget* stk = find_widget<stacked_widget>(
"main_stack",
false,
false)) {
1140 parent_of_addons_list = stk->get_layer_grid(0);
1145 if(
info ==
nullptr) {
1151 != find_widget<menu_button>(
"version_filter").get_value_string();
1155 stacked_widget& install_update_stack = find_widget<stacked_widget>(
"install_update_stack");
1157 find_widget<button>(
"update").set_active(updatable);
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
config get_addon_pbl_info(const std::string &addon_name, bool do_validate)
Gets the publish information for an add-on.
bool have_addon_in_vcs_tree(const std::string &addon_name)
Returns whether the specified add-on appears to be managed by a VCS or not.
bool have_addon_pbl_info(const std::string &addon_name)
Returns whether a .pbl file is present for the specified add-on or not.
std::vector< std::string > available_addons()
Returns a list of local add-ons that can be published.
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on's version information structs.
Add-ons (campaignd) client class.
install_result install_addon_with_checks(const addons_list &addons, const addon_info &addon)
Performs an add-on download and install cycle.
bool delete_remote_addon(const std::string &id, std::string &response_message)
Requests the specified add-on to be removed from the server.
const std::string & get_last_server_error_data() const
Returns the last error message extra data sent by the server, or an empty string.
@ abort
User aborted the operation because of an issue with dependencies or chose not to overwrite the add-on...
const std::string & server_id() const
bool using_tls() const
Returns whether the current connection uses TLS.
bool request_distribution_terms(std::string &terms)
Request the add-ons server distribution terms message.
const std::string & get_last_server_error() const
Returns the last error message sent by the server, or an empty string.
const std::string & addr() const
Returns the current hostname:port used for this connection.
bool request_addons_list(config &cfg, bool icons)
Request the add-ons list from the server.
const std::string & server_version() const
bool upload_addon(const std::string &id, std::string &response_message, config &cfg, bool local_only)
Uploads an add-on to the server.
A config object defines a single node in a WML file, with access to child nodes.
const_attr_itors attribute_range() const
child_itors child_range(config_key_type key)
boost::iterator_range< const_child_iterator > const_child_itors
config & add_child(config_key_type key)
void set_uninstall_function(addon_op_func_t function)
Sets the function to call when the player clicks the uninstall button.
std::function< bool(const addon_info &, const addon_info &)> addon_sort_func
void set_publish_function(addon_op_func_t function)
Sets the function to upload an addon to the addons server.
void add_list_to_keyboard_chain()
Adds the internal listbox to the keyboard event chain.
void set_install_function(addon_op_func_t function)
Sets the function to call when the player clicks the install button.
void set_delete_function(addon_op_func_t function)
Sets the function to install an addon from the addons server.
const addon_info * get_selected_addon() const
Returns the selected add-on.
void set_update_function(addon_op_func_t function)
Sets the function to call when the player clicks the update button.
void set_modified_signal_handler(const std::function< void()> &callback)
Sets up a callback that will be called when the player selects an add-on.
void set_callback_order_change(std::function< void(unsigned, sort_order::type)> callback)
Sets up a callback that will be called when the player changes the sorting order.
static const int DEFAULT_ACTION_RETVAL
Special retval for the toggle panels in the addons list.
static std::string colorize_addon_state_string(const std::string &str, ADDON_STATUS state, bool verbose=false)
Changes the color of an add-on state string (installed, outdated, etc.) according to the state itself...
void set_addons(const addons_list &addons)
Sets the add-ons to show.
virtual void set_active(const bool active) override
See styled_widget::set_active.
void uninstall_addon(const addon_info &addon)
void on_order_changed(unsigned int sort_column, sort_order::type order)
static const std::vector< std::pair< ADDON_STATUS_FILTER, std::string > > status_filter_types_
virtual void pre_show() override
Actions to be taken before showing the window.
boost::dynamic_bitset get_lang_filter_visibility() const
boost::dynamic_bitset get_status_filter_visibility() const
void update_selected_addon()
void execute_default_action(const addon_info &addon)
Called when the player double-clicks an add-on.
void reload_list_and_reselect_item(const std::string &id)
std::vector< std::pair< int, std::string > > language_filter_types_
void execute_default_action_on_selected_addon()
boost::dynamic_bitset get_name_filter_visibility() const
void toggle_details(button &btn, stacked_widget &stk)
void delete_addon(const addon_info &addon)
Performs all backend and UI actions for taking down the specified add-on.
addon_manager(addons_client &client)
config cfg_
Config which contains the list with the campaigns.
boost::dynamic_bitset get_type_filter_visibility() const
void install_selected_addon()
void delete_selected_addon()
void execute_action_on_selected_addon()
addons_tracking_list tracking_info_
static const std::vector< addon_order > all_orders_
void uninstall_selected_addon()
boost::dynamic_bitset get_tag_filter_visibility() const
void publish_selected_addon()
static const std::vector< std::pair< ADDON_TYPE, std::string > > type_filter_types_
void install_addon(const addon_info &addon)
void update_addon(const addon_info &addon)
bool need_wml_cache_refresh_
void publish_addon(const addon_info &addon)
Performs all backend and UI actions for publishing the specified add-on.
void on_selected_version_change()
@ yes_no_buttons
Shows a yes and no button.
@ ok_cancel_buttons
Shows an ok and cancel button.
Abstract base class for all modal dialogs.
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
void set_link_aware(bool l)
std::string get_value() const
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
A widget that allows the user to input text in single line.
base class of top level items, the only item which needs to store the final canvases to draw on.
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
void keyboard_capture(widget *widget)
void set_exit_hook(exit_hook mode, std::function< bool(window &)> func)
Sets the window's exit hook.
void close()
Requests to close the window.
status
The status of the window.
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
void set_addon_manager_saved_order_direction(sort_order::type value)
void set_password(const std::string &server, const std::string &login, const std::string &key)
sort_order::type addon_manager_saved_order_direction()
bool use_twelve_hour_clock_format()
std::string password(const std::string &server, const std::string &login)
Represents version numbers.
std::string str() const
Serializes the version number into string form.
Definitions for the interface to Wesnoth Markup Language (WML).
static std::string _(const char *str)
std::string label
What to show in the filter's drop-down list.
const std::vector< std::string > filtertext_
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
std::string id
Text to match against addon_info.tags()
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
void read_addons_list(const config &cfg, addons_list &dest)
Parse the specified add-ons list WML into an actual addons_list object.
std::map< std::string, addon_info > addons_list
language_list get_languages(bool all)
Return a list of available translations.
std::deque< std::unique_ptr< editor_action > > action_stack
Action stack typedef.
const std::string unicode_em_dash
static std::string describe_status_verbose(const addon_tracking_info &state)
REGISTER_DIALOG(editor_edit_unit)
static std::string format_addon_time(const std::chrono::system_clock::time_point &time)
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.
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_error_message(const std::string &msg, bool message_use_markup)
Shows an error 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.
retval
Default window/dialog return values.
@ OK
Dialog was closed with the OK button.
void show_help(const std::string &show_topic)
Open the help browser, show topic with id show_topic.
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
std::string strftime(const std::string &format, const std::tm *time)
bool ci_search(const std::string &s1, const std::string &s2)
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::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
addon_tracking_info get_addon_tracking_info(const addon_info &addon)
Get information about an add-on comparing its local state with the add-ons server entry.
bool is_installed_addon_status(ADDON_STATUS s)
@ ADDON_NOT_TRACKED
No tracking information available.
@ ADDON_INSTALLED_OUTDATED
Version in the server is older than local installation.
@ ADDON_NONE
Add-on is not installed.
@ ADDON_INSTALLED_UPGRADABLE
Version in the server is newer than local installation.
@ ADDON_INSTALLED
Version in the server matches local installation.
@ ADDON_INSTALLED_LOCAL_ONLY
No version in the server.
@ ADDON_INSTALLED_BROKEN
Dependencies not satisfied.
ADDON_STATUS_FILTER
Add-on installation status filters for the user interface.
std::map< std::string, addon_tracking_info > addons_tracking_list
std::vector< std::string > tags
version_info current_version
std::string display_type() const
Get an add-on type identifier for display in the user's language.
std::chrono::system_clock::time_point updated
std::string display_title_translated_or_original() const
std::chrono::system_clock::time_point created
std::string display_title_full() const
std::set< std::string > resolve_dependencies(const addons_list &addons) const
Resolve an add-on's dependency tree in a recursive fashion.
std::vector< std::string > locales
Stores additional status information about add-ons.
version_info installed_version
Contains the outcome of an add-on install operation.
install_outcome outcome
Overall outcome of the operation.
bool wml_changed
Specifies if WML on disk was altered and needs to be reloaded.
addon_list::addon_sort_func sort_func_desc
addon_list::addon_sort_func sort_func_asc
std::string as_preference
The value used in the preferences file.
Exception thrown when the WML parser fails to read a .pbl file.
static map_location::direction s
@ ADDON_THEME
GUI2 Themes.
@ ADDON_SP_SCENARIO
Single-player scenario.
@ ADDON_MP_SCENARIO
Multiplayer scenario.
@ ADDON_MP_CAMPAIGN
Multiplayer campaign.
@ ADDON_MP_FACTION
Multiplayer faction.
@ ADDON_MEDIA
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
@ ADDON_MP_ERA
Multiplayer era.
@ ADDON_CORE
Total Conversion Core.
@ ADDON_SP_CAMPAIGN
Single-player campaign.
@ ADDON_OTHER
an add-on that fits in no other category
@ ADDON_SP_MP_CAMPAIGN
Hybrid campaign.
@ ADDON_MP_MAPS
Multiplayer plain (no WML) map pack.
@ ADDON_MOD
Modification of the game.