The Battle for Wesnoth  1.15.2+dev
mp_join_game.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8  This program is distributed in the hope that it will be useful,
9  but WITHOUT ANY WARRANTY.
10 
11  See the COPYING file for more details.
12 */
13 
14 #define GETTEXT_DOMAIN "wesnoth-lib"
15 
17 
18 #include "chat_log.hpp"
19 #include "font/text_formatting.hpp"
20 #include "formatter.hpp"
21 #include "formula/string_utils.hpp"
22 #include "game_config.hpp"
23 #include "game_config_manager.hpp"
26 #include "gettext.hpp"
28 #include "gui/core/timer.hpp"
32 #include "gui/widgets/button.hpp"
33 #include "gui/widgets/chatbox.hpp"
35 #include "gui/widgets/image.hpp"
36 #include "gui/widgets/label.hpp"
37 #include "gui/widgets/listbox.hpp"
38 #include "gui/widgets/settings.hpp"
41 #include "log.hpp"
42 #include "mp_ui_alerts.hpp"
43 #include "statistics.hpp"
44 #include "units/types.hpp"
45 #include "utils/scope_exit.hpp"
46 #include "wesnothd_connection.hpp"
47 
48 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
49 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
50 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
51 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
52 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
53 
54 namespace gui2
55 {
56 namespace dialogs
57 {
58 
59 REGISTER_DIALOG(mp_join_game)
60 
61 mp_join_game::mp_join_game(saved_game& state, mp::lobby_info& lobby_info, wesnothd_connection& connection, const bool first_scenario, const bool observe_game)
62  : level_()
63  , state_(state)
64  , lobby_info_(lobby_info)
65  , network_connection_(connection)
66  , update_timer_(0)
67  , first_scenario_(first_scenario)
68  , observe_game_(observe_game)
69  , stop_updates_(false)
70  , player_list_(nullptr)
71  , flg_dialog_(nullptr)
72 {
73  set_show_even_without_video(true);
74 }
75 
77 {
78  if(update_timer_ != 0) {
80  update_timer_ = 0;
81  }
82 }
83 
84 /*
85  * Fetch the selected game's config from the server and prompts an initial faction selection.
86  */
88 {
89  // Ask for the next scenario data, if applicable
90  if(!first_scenario_) {
91  network_connection_.send_data(config("load_next_scenario"));
92  }
93 
94  bool has_scenario_and_controllers = false;
95  while(!has_scenario_and_controllers) {
96  config revc;
97  const bool data_res =
99 
100  if(!data_res) {
101  return false;
102  }
103 
104  mp::check_response(data_res, revc);
105 
106  if(revc.child("leave_game")) {
107  return false;
108  } else if(config& next_scenario = revc.child("next_scenario")) {
110  } else if(revc.has_attribute("version")) {
111  level_.swap(revc);
112 
113  has_scenario_and_controllers = true;
114  } else if(config& controllers = revc.child("controllers")) {
115  int index = 0;
116  for(const config& controller : controllers.child_range("controller")) {
117  if(config& side = get_scenario().child("side", index)) {
118  side["is_local"] = controller["is_local"];
119  }
120  ++index;
121  }
122 
123  has_scenario_and_controllers = true;
124  }
125  }
126 
127  if(level_["started"].to_bool()) {
129  return true;
130  }
131 
132  if(first_scenario_) {
133  state_.clear();
135 
136  // Make sure that we have the same config as host, if possible.
138  }
139 
141 
142  // If we're just an observer, we don't need to find an appropriate side and set faction selection
143  if(observe_game_) {
144  return true;
145  }
146 
147  // Search for an appropriate vacant slot. If a description is set (i.e. we're loading from a saved game),
148  // then prefer to get the side with the same description as our login. Otherwise just choose the first
149  // available side.
150  const config* side_choice = nullptr;
151 
152  int side_num_choice = 1, side_num_counter = 1;
153  for(const config& side : get_scenario().child_range("side")) {
154  // TODO: it can happen that the scenario specifies that the controller
155  // of a side should also gain control of another side.
156  if(side["controller"] == "reserved" && side["current_player"] == preferences::login()) {
157  side_choice = &side;
158  side_num_choice = side_num_counter;
159  break;
160  }
161 
162  if(side["controller"] == "human" && side["player_id"].empty()) {
163  if(!side_choice) { // Found the first empty side
164  side_choice = &side;
165  side_num_choice = side_num_counter;
166  }
167 
168  if(side["current_player"] == preferences::login()) {
169  side_choice = &side;
170  side_num_choice = side_num_counter;
171  break; // Found the preferred one
172  }
173  }
174 
175  if(side["player_id"] == preferences::login()) {
176  // We already own a side in this game
177  return true;
178  }
179 
180  ++side_num_counter;
181  }
182 
183  if(!side_choice) {
184  observe_game_ = true;
185  return true;
186  }
187 
188  // If the client is allowed to choose their team, do that here instead of having it set by the server
189  if((*side_choice)["allow_changes"].to_bool(true)) {
190  if(!show_flg_select(side_num_choice, true)) {
191  return false;
192  }
193  }
194 
195  return true;
196 }
197 
198 static std::string generate_user_description(const config& side)
199 {
200  // Allow the host to override, since only the host knows the ai_algorithm.
201  if(const config::attribute_value* desc = side.get("user_description")) {
202  return desc->str();
203  }
204 
205  const std::string controller_type = side["controller"].str();
206  const std::string reservation = side["current_player"].str();
207  const std::string owner = side["player_id"].str();
208 
209  if(controller_type == "ai") {
210  return _("Computer Player");
211  } else if(controller_type == "null") {
212  return _("Empty slot");
213  } else if(controller_type == "reserved") {
214  return VGETTEXT("Reserved for $playername", {{"playername", reservation}});
215  } else if(owner.empty()) {
216  return _("Vacant slot");
217  } else if(controller_type == "human" || controller_type == "network") {
218  return owner;
219  } else {
220  return _("empty");
221  }
222 }
223 
225 {
226  window.set_enter_disabled(true);
227  window.set_escape_disabled(true);
228 
229  //
230  // Set title
231  //
232  label& title = find_widget<label>(&window, "title", false);
233  title.set_label((formatter() << title.get_label() << " " << font::unicode_em_dash << " " << get_scenario()["name"].t_str()).str());
234 
235  //
236  // Set up sides list
237  //
238  generate_side_list(window);
239 
240  //
241  // Initialize chatbox and game rooms
242  //
243  chatbox& chat = find_widget<chatbox>(&window, "chat", false);
244 
247 
248  chat.room_window_open(N_("this game"), true, false);
249  chat.active_window_changed();
250  chat.load_log(default_chat_log, false);
251 
252  //
253  // Set up player list
254  //
255  player_list_.reset(new player_list_helper(&window));
256 
257  //
258  // Set up the network handling
259  //
260  update_timer_ = add_timer(game_config::lobby_network_timer, std::bind(&mp_join_game::network_handler, this, std::ref(window)), true);
261 
262  //
263  // Set up the Lua plugin context
264  //
265  plugins_context_.reset(new plugins_context("Multiplayer Join"));
266 
267  plugins_context_->set_callback("launch", [&window](const config&) { window.set_retval(retval::OK); }, false);
268  plugins_context_->set_callback("quit", [&window](const config&) { window.set_retval(retval::CANCEL); }, false);
269  plugins_context_->set_callback("chat", [&chat](const config& cfg) { chat.send_chat_message(cfg["message"], false); }, true);
270 }
271 
272 bool mp_join_game::show_flg_select(int side_num, bool first_time)
273 {
274  if(const config& side_choice = get_scenario().child("side", side_num - 1)) {
275  if(!side_choice["allow_changes"].to_bool(true)) {
276  return true;
277  }
278 
279  const config& era = level_.child("era");
280  if(!era) {
281  ERR_MP << "no era information\n";
282  return false;
283  }
284 
285  config::const_child_itors possible_sides = era.child_range("multiplayer_side");
286  if(possible_sides.empty()) {
287  WRN_MP << "no [multiplayer_side] found in era '" << era["id"] << "'.\n";
288  return false;
289  }
290 
291  const std::string color = side_choice["color"].str();
292 
293  std::vector<const config*> era_factions;
294  //make this safe against changes to level_ that might make possible_sides invalid pointers.
295  config era_copy;
296  for(const config& side : possible_sides) {
297  config& side_new = era_copy.add_child("multiplayer_side", side);
298  era_factions.push_back(&side_new);
299  }
300 
301  const bool is_mp = state_.classification().is_normal_mp_game();
302  const bool lock_settings = get_scenario()["force_lock_settings"].to_bool(!is_mp);
303  const bool use_map_settings = level_.child("multiplayer")["mp_use_map_settings"].to_bool();
304  const mp_game_settings::SAVED_GAME_MODE saved_game = level_.child("multiplayer")["savegame"].to_enum<mp_game_settings::SAVED_GAME_MODE>(mp_game_settings::SAVED_GAME_MODE::NONE);
305 
306  ng::flg_manager flg(era_factions, side_choice, lock_settings, use_map_settings, saved_game == mp_game_settings::SAVED_GAME_MODE::MIDGAME);
307 
308  {
309  gui2::dialogs::faction_select flg_dialog(flg, color, side_num);
310  flg_dialog_ = &flg_dialog;
311  utils::scope_exit se([this]() { flg_dialog_ = nullptr; });
312 
313  if(!flg_dialog.show() && !first_time) {
314  return true;
315  }
316  }
317 
318  config faction;
319  config& change = faction.add_child("change_faction");
320  change["change_faction"] = true;
321  change["name"] = preferences::login();
322  change["faction"] = flg.current_faction()["id"];
323  change["leader"] = flg.current_leader();
324  change["gender"] = flg.current_gender();
325  // TODO: the host cannot yet handle this and always uses the first side owned by that player.
326  change["side_num"] = side_num;
327 
329  }
330 
331  return true;
332 }
333 
335 {
336  if(stop_updates_) {
337  return;
338  }
339 
340  tree_view& tree = find_widget<tree_view>(&window, "side_list", false);
341 
342  window.keyboard_capture(&tree);
343 
344  tree.clear();
345  team_tree_map_.clear();
346  const std::map<std::string, string_map> empty_map;
347 
348  int side_num = 0;
349  for(const auto& side : get_scenario().child_range("side")) {
350  ++side_num;
351  if(!side["allow_player"].to_bool(true)) {
352  continue;
353  }
354 
355  // Check to see whether we've added a toplevel tree node for this team. If not, add one
356  if(team_tree_map_.find(side["team_name"].str()) == team_tree_map_.end()) {
357  std::map<std::string, string_map> data;
359 
360  item["label"] = (formatter() << _("Team:") << " " << t_string::from_serialized(side["user_team_name"])).str();
361  data.emplace("tree_view_node_label", item);
362 
363  tree_view_node& team_node = tree.add_node("team_header", data);
364  team_node.add_sibling("side_spacer", empty_map);
365 
366  team_tree_map_[side["team_name"].str()] = &team_node;
367  }
368 
369  std::map<std::string, string_map> data;
371 
372  const std::string color = !side["color"].empty() ? side["color"] : side["side"].str();
373 
374  item["label"] = (formatter() << "<span color='" << font::get_pango_color_from_id(color) << "'>" << side["side"] << "</span>").str();
375  data.emplace("side_number", item);
376 
377  std::string leader_image = ng::random_enemy_picture;
378  std::string leader_type = side["type"];
379  std::string leader_gender = side["gender"];
380  std::string leader_name;
381 
382  // If there is a unit which can recruit, use it as a leader.
383  // Necessary to display leader information when loading saves.
384  for(const config& side_unit : side.child_range("unit")) {
385  if(side_unit["canrecruit"].to_bool()) {
386  leader_type = side_unit["type"].str();
387  leader_gender = side_unit["gender"].str();
388  break;
389  }
390  }
391 
392  if(const unit_type* ut = unit_types.find(leader_type)) {
393  const unit_type& type = ut->get_gender_unit_type(leader_gender);
394 
395  leader_image = formatter() << type.image() << "~RC(" << type.flag_rgb() << ">" << color << ")";
396  leader_name = type.type_name();
397  }
398 
399  item["label"] = leader_image;
400  data.emplace("leader_image", item);
401 
402  std::string description = generate_user_description(side);
403  if(!leader_name.empty()) {
404  description += formatter() << " (<i>" << leader_name << "</i>)";
405  }
406 
407  item["label"] = description;
408  data.emplace("leader_type", item);
409 
410  item["label"] = (formatter() << "<span color='#a69275'>" << side["faction_name"] << "</span>").str();
411  data.emplace("leader_faction", item);
412 
413  std::string gender_icon = "icons/icon-random.png";
414  if(leader_gender != "null") {
415  gender_icon = formatter() << "icons/icon-" << leader_gender << ".png";
416  item["tooltip"] = leader_gender;
417  }
418 
419  item["label"] = gender_icon;
420  data.emplace("leader_gender", item);
421 
422  item.clear();
423 
424  // Don't show gold for saved games
425  // TODO: gold icon
426  if(side["allow_changes"].to_bool()) {
427  item["label"] = side["gold"].str() + " " + _("Gold");
428  data.emplace("side_gold", item);
429  }
430 
431  const int income_amt = side["income"];
432  if(income_amt != 0) {
433  const std::string income_string = formatter() << (income_amt > 0 ? "+" : "") << income_amt << " " << _("Income");
434 
435  item["label"] = income_string;
436  data.emplace("side_income", item);
437  }
438 
439  tree_view_node& node = team_tree_map_[side["team_name"].str()]->add_child("side_panel", data);
440 
441  grid& row_grid = node.get_grid();
442 
443  auto* select_leader_button = find_widget<button>(&row_grid, "select_leader", false, false);
444  if(select_leader_button) {
445  if(side["player_id"] == preferences::login() && side["allow_changes"].to_bool(true)) {
446  //
447  // Small wrapper function in order to set the handled and halt parameters and prevent
448  // crashes in case the dialog closes and the original button to which the callback was
449  // bound had already been destroyed. The other use of show_flg_select doesn't need these
450  // parameters, so it's easier not to declare them as function arguments.
451  //
452  const auto handler = [this, side_num](bool& handled, bool& halt) {
453  show_flg_select(side_num);
454  // note: this function is called from a std::function object stored in the widget
455  // and show_flg_select which internally calls
456  // show_dialog -> pump -> ... -> network_handler -> ... -> generate_side_list
457  // might destroy that std::function object while it is executed, this means that
458  // using the captured variables 'this' and 'side_num' after this will result in
459  // unexpected behaviour or crashes.
460  handled = halt = true;
461  };
462 
463  connect_signal_mouse_left_click(*select_leader_button, std::bind(handler, _3, _4));
464  } else {
465  select_leader_button->set_visible(widget::visibility::hidden);
466  }
467  }
468 
469  if(income_amt == 0) {
470  find_widget<image>(&row_grid, "income_icon", false).set_visible(widget::visibility::invisible);
471  find_widget<label>(&row_grid, "side_income", false).set_visible(widget::visibility::invisible);
472  }
473  }
474 }
475 
477 {
478  if(flg_dialog_) {
479  if(window* w = flg_dialog_->get_window()) {
480  w->set_retval(retval::CANCEL);
481  }
482  }
483 }
484 
486 {
487  // If the game has already started, close the dialog immediately.
488  if(level_["started"].to_bool()) {
489  window.set_retval(retval::OK);
490  return;
491  }
492 
493  config data;
494  if(!network_connection_.receive_data(data)) {
495  return;
496  }
497 
498  // Update chat
499  find_widget<chatbox>(&window, "chat", false).process_network_data(data);
500 
501  if(!data["message"].empty()) {
502  gui2::show_transient_message(_("Response") , data["message"]);
503  }
504 
505  if(data["failed"].to_bool()) {
507 
508  window.set_retval(retval::CANCEL);
509  } else if(data.child("start_game")) {
511 
512  level_["started"] = true;
513  window.set_retval(retval::OK);
514  } else if(data.child("leave_game")) {
516 
517  window.set_retval(retval::CANCEL);
518  }
519 
520  if(data.child("stop_updates")) {
521  stop_updates_ = true;
522  } else if(const config& c = data.child("scenario_diff")) {
523  // TODO: We should catch config::error and then leave the game.
525 
526  generate_side_list(window);
527  } else if(const config& change = data.child("change_controller")) {
528  if(config& side_to_change = get_scenario().find_child("side", "side", change["side"])) {
529  side_to_change.merge_with(change);
530  }
531 
532  if(flg_dialog_ && flg_dialog_->get_side_num() == change["side"].to_int()) {
534  }
535  } else if(data.has_child("scenario") || data.has_child("snapshot") || data.child("next_scenario")) {
536  level_ = first_scenario_ ? data : data.child("next_scenario");
537 
538  generate_side_list(window);
539  }
540 
541  if(data.has_child("turn")) {
542  ERR_MP << "received replay data\n" << data << "\n in mp join\n";
543  }
544 
545  // Update player list
546  if(data.has_child("user")) {
547  player_list_->update_list(data.child_range("user"));
548  }
549 }
550 
552 {
553  if(config& scenario = level_.child("scenario")) {
554  return scenario;
555  } else if(config& snapshot = level_.child("snapshot")) {
556  return snapshot;
557  }
558 
559  return level_;
560 }
561 
563 {
564  if(update_timer_ != 0) {
566  update_timer_ = 0;
567  }
568 
569  if(window.get_retval() == retval::OK) {
570  if(const config& stats = level_.child("statistics")) {
572  statistics::read_stats(stats);
573  }
574 
576 
578  } else if(observe_game_) {
579  network_connection_.send_data(config("observer_quit", config { "name", preferences::login() }));
580  } else {
581  network_connection_.send_data(config("leave_game"));
582  }
583 }
584 
585 } // namespace dialogs
586 } // namespace gui2
void send_data(const configr_of &request)
void active_window_changed()
Definition: chatbox.cpp:110
Dialog was closed with the CANCEL button.
Definition: retval.hpp:37
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:420
void set_lobby_info(mp::lobby_info &i)
Definition: chatbox.hpp:87
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1271
std::string era()
Definition: game.cpp:723
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:836
Variant for storing WML attributes.
bool has_attribute(config_key_type key) const
Definition: config.cpp:213
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:412
const std::string & flag_rgb() const
Definition: types.cpp:747
void game_has_begun()
bool fetch_data_with_loading_screen(config &cfg, loading_stage stage)
child_itors child_range(config_key_type key)
Definition: config.cpp:362
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, const bool restore_background)
Shows a transient message to the user.
const attribute_value * get(config_key_type key) const
Returns a pointer to the attribute with the given key or nullptr if it does not exist.
Definition: config.cpp:742
void fresh_stats()
Definition: statistics.cpp:779
unit_type_data unit_types
Definition: types.cpp:1529
void clear()
Definition: saved_game.cpp:752
faction_select * flg_dialog_
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:299
window * get_window() const
Returns a pointer to the dialog&#39;s window.
Label showing a text.
Definition: label.hpp:32
bool receive_data(config &result)
void level_to_gamestate(const config &level, saved_game &state)
FLG stands for faction, leader and gender.
Definition: flg_manager.hpp:28
bool show(const unsigned auto_close_time=0)
Shows the window.
wesnothd_connection & network_connection_
static void add_color_info(const config &v, bool build_defaults)
void generate_side_list(window &window)
A single unit type that the player may recruit.
Definition: types.hpp:42
Generic file dialog.
Definition: field-fwd.hpp:22
Pubic entry points for the MP workflow.
Definition: lobby_data.cpp:50
virtual void set_label(const t_string &label)
void network_handler(window &window)
Base container class.
Definition: grid.hpp:30
static game_config_manager * get()
void swap(config &cfg)
Definition: config.cpp:1377
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:91
std::string get_pango_color_from_id(const std::string &id)
Returns a hex color string from a color range.
This file contains the settings handling of the widget library.
std::ostringstream wrapper.
Definition: formatter.hpp:38
const t_string & type_name() const
The name of the unit in the current language setting.
Definition: types.hpp:135
std::unique_ptr< plugins_context > plugins_context_
unsigned lobby_network_timer
Definition: game_config.cpp:87
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal_function &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:233
A class that represents a TCP/IP connection to the wesnothd server.
bool show_flg_select(int side_num, bool first_time=false)
virtual void send_chat_message(const std::string &message, bool allies_only) override
Inherited form chat_handler.
Definition: chatbox.cpp:243
void load_log(std::map< std::string, chatroom_log > &log, bool show_lobby)
Definition: chatbox.cpp:95
void read_stats(const config &cfg)
Definition: statistics.cpp:769
mp::lobby_info & lobby_info_
void check_response(bool res, const config &data)
void close_faction_select_dialog_if_open()
Will close the Faction Select dialog if it&#39;s open.
void apply_diff(const config &diff, bool track=false)
A function to apply a diff config onto this config object.
Definition: config.cpp:1068
static map_location::DIRECTION se
Various uncategorised dialogs.
int get_retval()
Definition: window.hpp:370
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:210
std::string login()
std::unique_ptr< player_list_helper > player_list_
Default, unset return value.
Definition: retval.hpp:31
The user set the widget invisible, that means:
bool use_map_settings()
Definition: game.cpp:516
Contains the gui2 timer routines.
std::map< std::string, t_string > string_map
Definition: widget.hpp:24
std::size_t add_timer(const uint32_t interval, const std::function< void(std::size_t id)> &callback, const bool repeat)
Adds a new timer.
Definition: timer.cpp:126
static t_string from_serialized(const std::string &string)
Definition: tstring.hpp:149
int w
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:363
#define N_(String)
Definition: gettext.hpp:99
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
config & add_child(config_key_type key)
Definition: config.cpp:476
const std::string random_enemy_picture("units/random-dice.png")
const std::string & image() const
Definition: types.hpp:165
const t_string & get_label() const
void load_game_config_for_game(const game_classification &classification)
std::map< std::string, tree_view_node * > team_tree_map_
The user sets the widget hidden, that means:
game_classification & classification()
Definition: saved_game.hpp:55
const std::string unicode_em_dash
Definition: constants.cpp:40
std::map< std::string, chatroom_log > default_chat_log
Definition: chat_log.cpp:16
virtual void post_show(window &window) override
Inherited from modal_dialog.
void set_wesnothd_connection(wesnothd_connection &c)
Definition: chatbox.hpp:92
Standard logging facilities (interface).
tree_view_node & add_sibling(const std::string &id, const std::map< std::string, string_map > &data)
Adds a sibbling for a node at the end of the list.
#define ERR_MP
#define WRN_MP
virtual void pre_show(window &window) override
Inherited from modal_dialog.
static std::string generate_user_description(const config &side)
Dialog was closed with the OK button.
Definition: retval.hpp:34
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
mock_char c
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:62
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...
Definition: chatbox.cpp:383
tree_view_node & add_node(const std::string &id, const std::map< std::string, string_map > &data, const int index=-1)
Definition: tree_view.cpp:56
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:371
bool remove_timer(const std::size_t id)
Removes a timer.
Definition: timer.cpp:167
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:286