16 #define GETTEXT_DOMAIN "wesnoth-lib"
40 #include "addon/manager.hpp"
55 #define DBG_LB LOG_STREAM(debug, log_lobby)
56 #define LOG_LB LOG_STREAM(info, log_lobby)
57 #define ERR_LB LOG_STREAM(err, log_lobby)
58 #define SCOPE_LB log_scope2(log_lobby, __func__)
66 return show_prompt(
_(
"Do you really want to log out?"));
72 , gamelistbox_(nullptr)
75 , filter_friends_(register_bool(
"filter_with_friends",
77 []() {
return prefs::get().fi_friends_in_game();},
78 [](
bool v) {
prefs::get().set_fi_friends_in_game(v);},
80 , filter_ignored_(register_bool(
"filter_with_ignored",
82 []() {
return prefs::get().fi_blocked_in_game();},
83 [](
bool v) {
prefs::get().set_fi_blocked_in_game(v);},
85 , filter_slots_(register_bool(
"filter_vacant_slots",
88 [](
bool v) {
prefs::get().set_fi_vacant_slots(v);},
90 , filter_invert_(register_bool(
"filter_invert",
95 , filter_auto_hosted_(
false)
96 , filter_game_presets_(
false)
97 , filter_text_(
nullptr)
101 , player_list_dirty_(
true)
102 , gamelist_dirty_(
true)
103 , last_lobby_update_()
104 , gamelist_diff_update_(
true)
105 , network_connection_(connection)
106 , lobby_update_timer_(0)
107 , gamelist_id_at_row_()
108 , delay_playerlist_update_(
false)
109 , delay_gamelist_update_(
false)
110 , joined_game_id_(joined_game)
111 , queue_game_server_preset_()
113 set_show_even_without_video(
true);
114 set_allow_plugin_skip(
false);
115 set_always_save_fields(
true);
122 [
this](
auto&&...) { show_preferences_button_callback();
return true; });
149 for(
const auto& v : map) {
150 const std::string& key = v.first;
163 for(
const auto & vv : strmap) {
164 if(vv.first ==
"label") {
165 c->set_label(vv.second);
166 }
else if(vv.first ==
"tooltip") {
167 c->set_tooltip(vv.second);
173 bool handle_addon_requirements_gui(
const std::vector<mp::game_info::required_addon>& reqs,
mp::game_info::addon_req addon_outcome)
176 std::string e_title =
_(
"Incompatible User-made Content");
177 std::string err_msg =
_(
"This game cannot be joined because the host has out-of-date add-ons that are incompatible with your version. You might wish to suggest that the host’s add-ons be updated.");
180 err_msg +=
_(
"Details:");
192 std::string e_title =
_(
"Missing User-made Content");
193 std::string err_msg =
_(
"This game requires one or more user-made addons to be installed or updated in order to join.\nDo you want to try to install them?");
196 err_msg +=
_(
"Details:");
199 std::vector<std::string> needs_download;
204 needs_download.push_back(a.addon_id);
208 assert(needs_download.size() > 0);
244 LOG_LB <<
"Adding game to listbox (1)" <<
game.id;
268 int list_rows_deleted = 0;
272 std::vector<int> next_gamelist_id_at_row;
283 LOG_LB <<
"Adding game to listbox " <<
game.id;
287 DBG_LB <<
"Added a game listbox row not at the end" << list_i
298 next_gamelist_id_at_row.push_back(
game.id);
301 ERR_LB <<
"Ran out of listbox items -- triggering a full refresh";
307 ERR_LB <<
"gamelist_id_at_row_ overflow! " << list_i <<
" + "
310 <<
" -- triggering a full refresh";
316 if(
game.id != listbox_game_id) {
317 ERR_LB <<
"Listbox game id does not match expected id "
318 << listbox_game_id <<
" " <<
game.id <<
" (row " << list_i <<
")";
324 LOG_LB <<
"Modifying game in listbox " <<
game.id <<
" (row " << list_i <<
")";
329 next_gamelist_id_at_row.push_back(
game.id);
331 LOG_LB <<
"Deleting game from listbox " <<
game.id <<
" (row "
337 LOG_LB <<
"Clean game in listbox " <<
game.id <<
" (row " << list_i <<
")";
338 next_gamelist_id_at_row.push_back(
game.id);
344 for(
unsigned i = 0;
i < next_gamelist_id_at_row.size(); ++
i) {
352 ERR_LB <<
"Would select a row beyond the listbox" << select_row <<
" "
370 const std::string games_string =
VGETTEXT(
"Games: showing $num_shown out of $num_total", {
385 item[
"use_markup"] =
"true";
388 if(
game.vacant_slots > 0) {
392 const std::string scenario_text =
VGETTEXT(
"$game_name (Era: $era_name)", {
393 {
"game_name",
game.scenario},
394 {
"era_name",
game.era}
398 data.emplace(
"name", item);
401 data.emplace(
"scenario", item);
404 data.emplace(
"status", item);
419 std::ostringstream ss;
421 const auto mark_missing = [&ss]() {
434 auto mods =
game.mod_info;
437 ss <<
_(
"active_modifications^None") <<
"\n";
439 for(
const auto& mod : mods) {
451 const auto yes_or_no = [](
bool val) {
return val ?
_(
"yes") :
_(
"no"); };
454 ss <<
_(
"Experience modifier:") <<
" " <<
game.xp <<
"\n";
455 ss <<
_(
"Gold per village:") <<
" " <<
game.gold <<
"\n";
456 ss <<
_(
"Map size:") <<
" " <<
game.map_size_info <<
"\n";
457 ss <<
_(
"Reloaded:") <<
" " << yes_or_no(
game.reloaded) <<
"\n";
458 ss <<
_(
"Shared vision:") <<
" " <<
game.vision <<
"\n";
459 ss <<
_(
"Shuffle sides:") <<
" " << yes_or_no(
game.shuffle_sides) <<
"\n";
460 ss <<
_(
"Time limit:") <<
" " <<
game.time_limit <<
"\n";
461 ss <<
_(
"Use map settings:") <<
" " << yes_or_no(
game.use_map_settings);
465 if(!
game.have_era || !
game.have_all_mods || !
game.required_addons.empty()) {
466 info_icon.
set_label(
"icons/icon-info-error.png");
469 ss <<
_(
"One or more add-ons need to be installed\nin order to join this game.");
471 info_icon.
set_label(
"icons/icon-info.png");
481 if(
game.password_required) {
496 observer_icon.
set_label(
"misc/no_observer.png");
514 if(
game.game_preset) {
537 DBG_LB <<
"mp_lobby::update_gamelist_filter";
562 bool can_join =
false, can_observe =
false;
566 can_observe =
game.can_observe();
567 can_join =
game.can_join();
573 find_widget<button>(
"observe_global").set_active(can_observe);
574 find_widget<button>(
"join_global").set_active(can_join);
583 gamelistbox_ = find_widget<listbox>(
"game_list",
false,
true);
595 chatbox_ = find_widget<chatbox>(
"chat",
false,
true);
602 find_widget<button>(
"create").set_retval(
CREATE);
605 find_widget<button>(
"show_preferences"),
609 find_widget<button>(
"join_global"),
612 find_widget<button>(
"join_global").set_active(
false);
615 find_widget<button>(
"observe_global"),
619 find_widget<button>(
"server_info"),
622 find_widget<button>(
"observe_global").set_active(
false);
624 menu_button& replay_options = find_widget<menu_button>(
"replay_options");
637 filter_text_ = find_widget<text_box>(
"filter_text",
false,
true);
662 if(
panel* profile_panel = find_widget<panel>(
"profile",
false,
false)) {
664 [](
const auto& u) { return u.get_relation() == mp::user_info::user_relation::ME; });
667 profile_panel->find_widget<
label>(
"username").
set_label(your_info->name);
669 auto& profile_button = profile_panel->find_widget<
button>(
"view_profile");
672 auto& history_button = profile_panel->find_widget<
button>(
"view_match_history");
677 listbox& tab_bar = find_widget<listbox>(
"games_list_tab_bar");
681 button* queue_join_button = find_widget<button>(
"join_queue",
false,
true);
685 button* queue_leave_button = find_widget<button>(
"leave_queue",
false,
true);
730 listbox* queues_listbox = find_widget<listbox>(
"queue_list",
false,
true);
732 if(queues.size() >
static_cast<std::size_t
>(queues_listbox->
get_selected_row())) {
736 std::vector<mp::game_info::required_addon>
missing;
757 ERR_LB <<
"Attempted to join queue but couldn't find queue info";
763 listbox* queues_listbox = find_widget<listbox>(
"queue_list",
false,
true);
765 if(queues.size() >
static_cast<std::size_t
>(queues_listbox->
get_selected_row())) {
769 ERR_LB <<
"Attempted to join queue but couldn't find queue info";
796 LOG_LB <<
"caught wesnothd_error in network_handler: " <<
e.message;
826 listbox* queues_listbox = find_widget<listbox>(
"queue_list",
false,
true);
827 queues_listbox->
clear();
830 std::sort(queues.begin(), queues.end(), [](
const mp::queue_info& q1,
const mp::queue_info& q2) { return q1.display_name < q2.display_name; });
836 item[
"label"] =
info.current_players.count(
prefs::get().login()) > 0 ?
"x" :
"o";
837 data.emplace(
"is_in_queue", item);
839 item[
"label"] =
info.display_name;
840 data.emplace(
"queue_name", item);
843 data.emplace(
"queue_player_count", item);
851 if(
auto error =
data.optional_child(
"error")) {
853 }
else if(
data.has_child(
"gamelist")) {
855 }
else if(
auto gamelist_diff =
data.optional_child(
"gamelist_diff")) {
857 }
else if(
auto info =
data.optional_child(
"message")) {
858 if(
info[
"type"] ==
"server_info") {
861 }
else if(
info[
"type"] ==
"announcements") {
865 }
else if(
auto create =
data.optional_child(
"create_game")) {
870 }
else if(
auto join_game =
data.optional_child(
"join_game")) {
873 }
else if(
auto queue_update =
data.optional_child(
"queue_update")) {
875 if(queue_update[
"action"].str() ==
"add") {
877 new_info.
id = queue_update[
"queue_id"].to_int();
879 new_info.
display_name = queue_update[
"display_name"].str();
882 if(
info.id == queue_update[
"queue_id"].to_int()) {
883 if(queue_update[
"action"].str() ==
"remove") {
885 }
else if(queue_update[
"action"].str() ==
"update") {
886 if(queue_update->has_attribute(
"display_name")) {
887 info.display_name = queue_update[
"display_name"].str();
889 if(queue_update->has_attribute(
"players_required")) {
890 info.players_required = queue_update[
"players_required"].to_int();
892 if(queue_update->has_attribute(
"current_players")){
895 if(queue_update->has_attribute(
"addons")){
918 DBG_LB <<
"Received gamelist";
928 DBG_LB <<
"Received gamelist diff";
931 ERR_LB <<
"process_gamelist_diff failed!";
934 const int joined =
data.child_count(
"insert_child");
935 const int left =
data.child_count(
"remove_child");
936 if(joined > 0 || left > 0) {
960 if(!
game.can_join()) {
961 ERR_LB <<
"Attempted to join a game with no vacant slots";
967 if(!
game.can_observe()) {
968 ERR_LB <<
"Attempted to observe a game with observers disabled";
974 if(
game.can_join()) {
976 }
else if(
game.can_observe()) {
979 DBG_LB <<
"Cannot join or observe a game.";
998 if(
game.required_addons.empty()) {
999 gui2::show_error_message(
_(
"Something is wrong with the addon version check database supporting the multiplayer lobby. Please report this at https://bugs.wesnoth.org."));
1003 if(!handle_addon_requirements_gui(
game.required_addons,
game.addons_outcome)) {
1015 join_data[
"observe"] = try_obsv;
1021 }
else if(!join_data.
empty() &&
game.password_required) {
1022 std::string password;
1024 if(!gui2::dialogs::mp_join_game_password_prompt::execute(password)) {
1028 join_data[
"password"] = password;
1031 join_data[
"mp_scenario"] =
game.scenario_id;
1049 ERR_LB <<
"Attempted to join/observe a game with an invalid id: " << game_id;
1083 gui2::dialogs::preferences_dialog::display();
1099 if(!info.match_string_filter(s)) {
1108 return filter_friends_->get_widget_value() ?
info.has_friends ==
true :
true;
1115 return filter_ignored_->get_widget_value() ==
false ?
info.has_ignored ==
false :
true;
1119 return filter_slots_->get_widget_value() ?
info.vacant_slots > 0 :
true;
1123 return info.auto_hosted == filter_auto_hosted_;
1127 return info.game_preset == filter_game_presets_;
1130 lobby_info_.set_game_filter_invert(
1131 [
this](
bool val) {
return filter_invert_->get_widget_value() ? !val : val; });
1136 if(key == SDLK_RETURN || key == SDLK_KP_ENTER) {
1157 if(
info->game_id != 0) {
1172 const int value = find_widget<menu_button>(
"replay_options").get_value();
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
std::map< std::string, chatroom_log > default_chat_log
A config object defines a single node in a WML file, with access to child nodes.
bool has_attribute(std::string_view key) 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.
lobby_chat_window * room_window_open(const std::string &room, const bool open_new, const bool allow_close=true)
Check if a room window for "room" is open, if open_new is true then it will be created if not found.
virtual void send_chat_message(const std::string &message, bool allies_only) override
Inherited form chat_handler.
lobby_chat_window * whisper_window_open(const std::string &name, bool open_new)
Check if a whisper window for user "name" is open, if open_new is true then it will be created if not...
void switch_to_window(lobby_chat_window *t)
Switch to the window given by a valid pointer (e.g.
void process_network_data(const ::config &data)
void set_active_window_changed_callback(const std::function< void(void)> &f)
void load_log(std::map< std::string, chatroom_log > &log, bool show_lobby)
void active_window_changed()
bool result_open_whisper() const
@ yes_no_buttons
Shows a yes and no button.
@ auto_close
Enables auto close.
Abstract base class for all modal dialogs.
bool show(const unsigned auto_close_time=0)
Shows the window.
void enter_selected_game(JOIN_MODE mode)
Enter game by index, where index is the selected game listbox row.
void process_gamelist(const config &data)
void update_visible_games()
void update_selected_game()
virtual void post_show() override
Actions to be taken after the window has been shown.
mp::lobby_info & lobby_info_
lobby_player_list_helper player_list_
void skip_replay_changed_callback()
static std::string announcements_
bool delay_gamelist_update_
wesnothd_connection & network_connection_
void open_match_history()
void update_gamelist_filter()
bool gamelist_diff_update_
bool delay_playerlist_update_
void process_network_data(const config &data)
widget_data make_game_row_data(const mp::game_info &game)
void select_game_by_player(const mp::user_info *info)
void game_filter_keypress_callback(const SDL_Keycode key)
void enter_game_by_id(const int game_id, JOIN_MODE mode)
Entry wrapper for enter_game, where game is located by game id.
void update_gamelist_diff()
void show_preferences_button_callback()
static std::string server_information_
void enter_game(const mp::game_info &game, JOIN_MODE mode)
Exits the lobby and enters the given game.
bool filter_game_presets_
void user_dialog_callback(const mp::user_info *info)
void tab_switch_callback()
mp_lobby(mp::lobby_info &info, wesnothd_connection &connection, int &joined_game)
void process_gamelist_diff(const config &data)
std::size_t lobby_update_timer_
Timer for updating the lobby.
config queue_game_server_preset_
virtual void pre_show() override
Actions to be taken before showing the window.
void adjust_game_row_contents(const mp::game_info &game, grid *grid, bool add_callbacks=true)
void delete_preset(const int game_id)
std::vector< int > gamelist_id_at_row_
std::chrono::steady_clock::time_point last_lobby_update_
void network_handler()
Network polling callback.
@ RELOAD_CONFIG
player clicked the Create button
static void display(const std::string &player_name, wesnothd_connection &connection, bool wait_for_response=true)
The display function.
std::unique_ptr< plugins_context > plugins_context_
static void display(const std::string &info, const std::string &announcements)
The display function.
void register_hotkey(const hotkey::HOTKEY_COMMAND id, Func &&function)
Registers a hotkey.
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
bool select_row(const unsigned row, const bool select=true)
Selects a row.
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
void clear()
Removes all the rows in the listbox, clearing it.
int get_selected_row() const
Returns the first selected row.
unsigned get_item_count() const
Returns the number of items in the listbox.
const mp::user_info * get_selected_info() const
void update(const std::vector< mp::user_info > &user_info, int focused_game)
Updates the tree contents based on the given user data.
void set_map_data(const std::string &map_data)
std::string get_value() const
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
show_mode mode() const
Returns the dialog mode for this window.
void keyboard_capture(widget *widget)
void set_exit_hook(exit_hook mode, const Func &hook)
Sets the window's exit hook.
This class represents the collective information the client has about the players and games on the se...
void add_game_filter(game_filter_func func)
Adds a new filter function to be considered when apply_game_filter is called.
bool is_game_visible(const game_info &)
Returns whether the game would be visible after the game filters are applied.
void clear_game_filters()
Clears all game filter functions.
const config & gamelist() const
Returns the raw game list config data.
void process_gamelist(const config &data)
Process a full game list.
bool process_gamelist_diff(const config &data)
Process a gamelist diff.
std::function< void()> begin_state_sync()
Updates the game pointer list and returns a second stage cleanup function to be called after any acti...
game_info * get_game_by_id(int id)
Returns info on a game with the given game ID.
bool gamelist_initialized() const
const std::vector< game_info * > & games() const
void apply_game_filter()
Generates a new list of games that match the current filter functions and inversion setting.
const boost::dynamic_bitset & games_visibility() const
const std::vector< user_info > & users() const
void remove_game_preset(int id)
optional_const_config get_game_preset(int id)
Implements a quit confirmation dialog.
static bool quit()
Shows the quit confirmation if needed.
A class that represents a TCP/IP connection to the wesnothd server.
bool receive_data(config &result)
Receives the next pending data pack from the server, if available.
Networked add-ons (campaignd) client interface.
Implements some helper classes to ease adding fields to a dialog and hide the synchronization needed.
static std::string _(const char *str)
static lg::log_domain log_lobby("lobby")
bool ad_hoc_addon_fetch_session(const std::vector< std::string > &addon_ids)
Conducts an ad-hoc add-ons server connection to download an add-on with a particular id and all it's ...
bool open_object([[maybe_unused]] const std::string &path_or_url)
const color_t YELLOW_COLOR
const color_t TITLE_COLOR
const std::string unicode_bullet
std::chrono::milliseconds lobby_refresh
std::chrono::milliseconds lobby_network_timer
REGISTER_DIALOG(editor_edit_unit)
void connect_signal_pre_key_press(dispatcher &dispatcher, const signal_keyboard &signal)
Connects the signal for 'snooping' on the keypress.
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 connect_signal_mouse_left_double_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button double click.
std::size_t add_timer(const std::chrono::milliseconds &interval, const std::function< void(std::size_t id)> &callback, const bool repeat)
Adds a new timer.
std::map< std::string, widget_item > widget_data
std::map< std::string, t_string > widget_item
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
bool remove_timer(const std::size_t id)
Removes a timer.
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.
void show_help(const std::string &show_topic)
Open the help browser.
Functions to load and save images from/to disk.
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified tag.
std::string span_size(std::string_view size, Args &&... data)
Applies Pango markup to the input specifying its display size.
void do_notify(notify_mode mode, const std::string &sender, const std::string &message)
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
std::vector< queue_info > & get_server_queues()
Gets the list of server-side queues received on login.
std::string get_profile_link(int user_id)
Gets the forum profile link for the given user.
bool logged_in_as_moderator()
Gets whether the currently logged-in user is a moderator.
std::size_t size(std::string_view str)
Length in characters of 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.
auto * find_if(Container &container, const Predicate &predicate)
Convenience wrapper for using find_if on a container without needing to comare to end()
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
std::vector< std::string > split(const config_attribute_value &val)
std::string to_string(const Range &range, const Func &op)
Desktop environment interaction functions.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
The basic class for representing 8-bit RGB or RGBA colour values.
lobby_delay_gamelist_update_guard(mp_lobby &l)
~lobby_delay_gamelist_update_guard()
This class represents the info a client has about a game on the server.
std::set< std::string > required_addons
This class represents the information a client has about another player.
An error occurred during when trying to communicate with the wesnothd server.
static map_location::direction s
Contains the gui2 timer routines.