The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
connect_engine.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2016 by Andrius Silinskas <[email protected]>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 #include "connect_engine.hpp"
15 
16 #include "ai/configuration.hpp"
17 #include "config_assign.hpp"
18 #include "formula/string_utils.hpp"
19 #include "game_preferences.hpp"
20 #include "gettext.hpp"
21 #include "log.hpp"
22 #include "map/map.hpp"
23 #include "multiplayer_ui.hpp"
24 #include "mp_game_utils.hpp"
25 #include "mt_rng.hpp"
26 #include "playcampaign.hpp"
27 #include "tod_manager.hpp"
28 #include "multiplayer_ui.hpp" // For get_color_string
29 #include "wesnothd_connection.hpp"
30 
31 #include <boost/assign.hpp>
32 #include <stdlib.h>
33 #include <ctime>
34 
35 static lg::log_domain log_config("config");
36 #define LOG_CF LOG_STREAM(info, log_config)
37 #define WRN_CF LOG_STREAM(warn, log_config)
38 #define ERR_CF LOG_STREAM(err, log_config)
39 
40 static lg::log_domain log_mp_connect_engine("mp/connect/engine");
41 #define DBG_MP LOG_STREAM(debug, log_mp_connect_engine)
42 #define LOG_MP LOG_STREAM(info, log_mp_connect_engine)
43 #define WRN_MP LOG_STREAM(warn, log_mp_connect_engine)
44 #define ERR_MP LOG_STREAM(err, log_mp_connect_engine)
45 
46 static lg::log_domain log_network("network");
47 #define LOG_NW LOG_STREAM(info, log_network)
48 
49 namespace {
50 
51 const std::string controller_names[] = {
52  "human",
53  "human",
54  "ai",
55  "null",
56  "reserved"
57 };
58 
59 const std::string attributes_to_trim[] = {
60  "side",
61  "type",
62  "gender",
63  "recruit",
64  "player_id",
65  "previous_recruits",
66  "controller",
67  "current_player",
68  "team_name",
69  "user_team_name",
70  "color",
71  "gold",
72  "income",
73  "allow_changes"
74 };
75 
76 }
77 
78 namespace ng {
79 
81  const bool first_scenario, mp_campaign_info* campaign_info) :
82  level_(),
83  state_(state),
84  params_(state.mp_settings()),
85  default_controller_(campaign_info ? CNTR_NETWORK : CNTR_LOCAL),
86  campaign_info_(campaign_info),
87  first_scenario_(first_scenario),
88  force_lock_settings_(),
89  side_engines_(),
90  era_factions_(),
91  team_names_(),
92  user_team_names_(),
93  player_teams_()
94 {
95  // Initial level config from the mp_game_settings.
97  if (level_.empty()) {
98  return;
99  }
100 
101  const bool is_mp = state_.classification().is_normal_mp_game();
102  force_lock_settings_ = (!state.mp_settings().saved_game) && scenario()["force_lock_settings"].to_bool(!is_mp);
103 
104  // Original level sides.
106 
107  // AI algorithms.
110  level_.child_range("modification"));
111 
112  // Set the team name lists and modify the original level sides,
113  // if necessary.
114  std::vector<std::string> original_team_names;
115  std::string team_prefix(std::string(_("Team")) + " ");
116  int side_count = 1;
117  for (config& side : sides) {
118  const std::string side_str = std::to_string(side_count);
119  config::attribute_value& team_name = side["team_name"];
120  config::attribute_value& user_team_name =
121  side["user_team_name"];
122 
123  // Revert to default values if appropriate.
124  if (team_name.empty()) {
125  team_name = side_str;
126  }
127  if (params_.use_map_settings && user_team_name.empty()) {
128  user_team_name = team_name;
129  }
130 
131  bool add_team = true;
133  // Only add a team if it is not found.
134  bool found = std::find(team_names_.begin(), team_names_.end(),
135  team_name.str()) != team_names_.end();
136 
137  if (found) {
138  add_team = false;
139  }
140  } else {
141  // Always add a new team for every side, but leave
142  // the specified team assigned to a side if there is one.
143  std::vector<std::string>::const_iterator name_itor =
144  std::find(original_team_names.begin(),
145  original_team_names.end(), team_name.str());
146  if (name_itor == original_team_names.end()) {
147  original_team_names.push_back(team_name);
148 
149  team_name = "Team " +
150  std::to_string(original_team_names.size());
151  } else {
152  team_name = "Team " + std::to_string(
153  name_itor - original_team_names.begin() + 1);
154  } // Note that the prefix "Team " is untranslatable, as team_name is not meant to be translated. This is needed so that the attribute
155  // is not interpretted as an int when reading from config, which causes bugs later.
156 
157  user_team_name = team_prefix + side_str;
158  }
159 
160  if (add_team) {
161  team_names_.push_back(params_.use_map_settings ? team_name :
162  "Team " + side_str);
163  user_team_names_.push_back(user_team_name.t_str().to_serialized());
164 
165  if (side["allow_player"].to_bool(true) || game_config::debug) {
166  player_teams_.push_back(user_team_name.str());
167  }
168  }
169 
170  ++side_count;
171  }
172 
173  // Selected era's factions.
174  for (const config& era : level_.child("era").child_range("multiplayer_side")) {
175 
176  era_factions_.push_back(&era);
177  }
178 
180  // Create side engines.
181  int index = 0;
182  for (const config &s : sides) {
183  side_engine_ptr new_side_engine(new side_engine(s, *this, index));
184  side_engines_.push_back(new_side_engine);
185 
186  index++;
187  }
188 
189  if(first_scenario_) {
190  // Add host to the connected users list.
192  }
193  else {
194  // Add host but don't assign a side to him.
196  // Load reserved players information into the sides.
198  }
199 
200  //actualy only updates the sides in the level.
201  update_level();
202 
203  // If we are connected, send data to the connected host.
204  send_level_data();
205 }
206 
208 {
209 }
210 
212  if(config& s = scenario())
213  return &s;
214  else
215  return nullptr;
216 }
217 
219  int side_taken)
220 {
221  config user_data;
222  user_data["name"] = name;
223  import_user(user_data, observer, side_taken);
224 }
225 
227  int side_taken)
228 {
229  const std::string& username = data["name"];
230  assert(!username.empty());
231  if(campaign_info_) {
232  connected_users_rw().insert(username);
233  }
235 
236  if (observer) {
237  return;
238  }
239 
240  bool side_assigned = false;
241  if (side_taken >= 0) {
242  side_engines_[side_taken]->place_user(data, true);
243  side_assigned = true;
244  }
245 
246  // Check if user has a side(s) reserved for him.
247  for (side_engine_ptr side : side_engines_) {
248  if (side->reserved_for() == username && side->player_id().empty() && side->controller() != CNTR_COMPUTER) {
249  side->place_user(data);
250 
251  side_assigned = true;
252  }
253  }
254 
255  // If no sides were assigned for a user,
256  // take a first available side.
257  if (side_taken < 0 && !side_assigned) {
258  for (side_engine_ptr side : side_engines_) {
259  if (side->available_for_user(username) ||
260  side->controller() == CNTR_LOCAL) {
261  side->place_user(data);
262 
263  side_assigned = true;
264  break;
265  }
266  }
267  }
268 
269  // Check if user has taken any sides, which should get control
270  // over any other sides.
271  for (side_engine_ptr user_side : side_engines_) {
272  if(user_side->player_id() == username && !user_side->previous_save_id().empty()) {
273  for (side_engine_ptr side : side_engines_){
274  if(side->player_id().empty() && side->previous_save_id() == user_side->previous_save_id()) {
275  side->place_user(data);
276  }
277  }
278  }
279  }
280 }
281 
283 {
284  for (side_engine_ptr side : side_engines_) {
285  if (side->available_for_user()) {
286  return true;
287  }
288  }
289 
290  return false;
291 }
292 
294 {
295  DBG_MP << "updating level" << std::endl;
296 
297  scenario().clear_children("side");
298 
299  for (side_engine_ptr side : side_engines_) {
300  scenario().add_child("side", side->new_config());
301  }
302 }
303 
304 void connect_engine::update_and_send_diff(bool /*update_time_of_day*/)
305 {
306  config old_level = level_;
307  update_level();
308 
309  config diff = level_.get_diff(old_level);
310  if (!diff.empty()) {
311  config scenario_diff;
312  scenario_diff.add_child("scenario_diff", diff);
313  send_to_server(scenario_diff);
314  }
315 }
316 
318 {
319  if (side_engines_.size() == 0) {
320  return true;
321  }
322 
323  // First check if all sides are ready to start the game.
324  for (side_engine_ptr side : side_engines_) {
325  if (!side->ready_for_start()) {
326  const int side_num = side->index() + 1;
327  DBG_MP << "not all sides are ready, side " <<
328  side_num << " not ready\n";
329 
330  return false;
331  }
332  }
333 
334  DBG_MP << "all sides are ready" << std::endl;
335 
336  /*
337  * If at least one human player is slotted with a player/ai we're allowed
338  * to start. Before used a more advanced test but it seems people are
339  * creative in what is used in multiplayer [1] so use a simpler test now.
340  * [1] http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=568029
341  */
342  for (side_engine_ptr side : side_engines_) {
343  if (side->controller() != CNTR_EMPTY && side->allow_player()) {
344  return true;
345  }
346  }
347 
348  return false;
349 }
350 
352 {
353  if (campaign_info_) {
355  }
356 }
357 
359 {
360  if (campaign_info_) {
362  }
363  else {
364  return false;
365  }
366 }
367 
368 
369 std::vector<std::string> side_engine::get_children_to_swap()
370 {
371  std::vector<std::string> children;
372 
373  children.push_back("village");
374  children.push_back("unit");
375  children.push_back("ai");
376 
377  return children;
378 }
379 
380 std::multimap<std::string, config> side_engine::get_side_children()
381 {
382  std::multimap<std::string, config> children;
383 
384  for (const std::string& children_to_swap : get_children_to_swap()) {
385  for (const config& child : cfg_.child_range(children_to_swap)) {
386  children.insert(std::pair<std::string, config>(children_to_swap, child));
387  }
388  }
389 
390  return children;
391 }
392 
393 void side_engine::set_side_children(std::multimap<std::string, config> children)
394 {
395  for (const std::string& children_to_remove : get_children_to_swap()) {
396  cfg_.clear_children(children_to_remove);
397  }
398 
399  for (std::pair<std::string, config> child_map : children) {
400  cfg_.add_child(child_map.first, child_map.second);
401  }
402 }
403 
404 
406 {
407  DBG_MP << "starting a new game" << std::endl;
408 
409  // Resolves the "random faction", "random gender" and "random message"
410  // Must be done before shuffle sides, or some cases will cause errors
411  rand_rng::mt_rng rng; // Make an RNG for all the shuffling and random faction operations
412  for (side_engine_ptr side : side_engines_) {
413  std::vector<std::string> avoid_faction_ids;
414 
415  // If we aren't resolving random factions independently at random, calculate which factions should not appear for this side.
416  if (params_.random_faction_mode != mp_game_settings::RANDOM_FACTION_MODE::DEFAULT) {
417  for (side_engine_ptr side2 : side_engines_) {
418  if (!side2->flg().is_random_faction()) {
419  switch(params_.random_faction_mode.v) {
420  case mp_game_settings::RANDOM_FACTION_MODE::NO_MIRROR:
421  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
422  break;
423  case mp_game_settings::RANDOM_FACTION_MODE::NO_ALLY_MIRROR:
424  if (side2->team() == side->team()) {// TODO: When the connect engines are fixed to allow multiple teams, this should be changed to "if side1 and side2 are allied, i.e. their list of teams has nonempty intersection"
425  avoid_faction_ids.push_back(side2->flg().current_faction()["id"].str());
426  }
427  break;
428  default:
429  break; // assert(false);
430  }
431  }
432  }
433  }
434  side->resolve_random(rng, avoid_faction_ids);
435  }
436 
437  // Shuffle sides (check settings and if it is a re-loaded game).
438  // Must be done after resolve_random() or shuffle sides, or they won't work.
439  if (state_.mp_settings().shuffle_sides && !force_lock_settings_ && !(level_.child("snapshot") && level_.child("snapshot").child("side"))) {
440 
441  // Only playable sides should be shuffled.
442  std::vector<int> playable_sides;
443  for (side_engine_ptr side : side_engines_) {
444  if (side->allow_player() && side->allow_shuffle()) {
445  playable_sides.push_back(side->index());
446  }
447  }
448 
449  // Fisher-Yates shuffle.
450  for (int i = playable_sides.size(); i > 1; i--)
451  {
452  int j_side = playable_sides[rng.get_next_random() % i];
453  int i_side = playable_sides[i - 1];
454 
455  if (i_side == j_side) continue; //nothing to swap
456 
457  // First we swap everything about a side with another
458  side_engine_ptr tmp_side = side_engines_[j_side];
459  side_engines_[j_side] = side_engines_[i_side];
460  side_engines_[i_side] = tmp_side;
461 
462  // Some 'child' variables such as village ownership and
463  // initial side units need to be swapped over as well
464  std::multimap<std::string, config> tmp_side_children = side_engines_[j_side]->get_side_children();
465  side_engines_[j_side]->set_side_children(side_engines_[i_side]->get_side_children());
466  side_engines_[i_side]->set_side_children(tmp_side_children);
467 
468  // Then we revert the swap for fields that are unique to
469  // player control and the team they selected
470  int tmp_index = side_engines_[j_side]->index();
471  side_engines_[j_side]->set_index(side_engines_[i_side]->index());
472  side_engines_[i_side]->set_index(tmp_index);
473 
474  int tmp_team = side_engines_[j_side]->team();
475  side_engines_[j_side]->set_team(side_engines_[i_side]->team());
476  side_engines_[i_side]->set_team(tmp_team);
477  }
478  }
479 
480  // Make other clients not show the results of resolve_random().
481  config lock("stop_updates");
482  send_to_server(lock);
483 
484  update_and_send_diff(true);
485 
487 
488  // Build the gamestate object after updating the level.
490 
491  send_to_server(config("start_game"));
492 }
493 
495  const commandline_options& cmdline_opts)
496 {
497  DBG_MP << "starting a new game in commandline mode" << std::endl;
498 
499  typedef boost::tuple<unsigned int, std::string> mp_option;
500 
501  rand_rng::mt_rng rng;
502 
503  unsigned num = 0;
504  for (side_engine_ptr side : side_engines_) {
505  num++;
506 
507  // Set the faction, if commandline option is given.
508  if (cmdline_opts.multiplayer_side) {
509  for (const mp_option& option : *cmdline_opts.multiplayer_side) {
510 
511  if (option.get<0>() == num) {
512  if (std::find_if(era_factions_.begin(), era_factions_.end(), [&option](const config* faction) { return (*faction)["id"] == option.get<1>(); }) != era_factions_.end()) {
513  DBG_MP << "\tsetting side " << option.get<0>() << "\tfaction: " << option.get<1>() << std::endl;
514 
515  side->set_faction_commandline(option.get<1>());
516  }
517  else {
518  ERR_MP << "failed to set side " << option.get<0>() << " to faction " << option.get<1>() << std::endl;
519  }
520  }
521  }
522  }
523 
524  // Set the controller, if commandline option is given.
525  if (cmdline_opts.multiplayer_controller) {
526  for (const mp_option& option : *cmdline_opts.multiplayer_controller) {
527 
528  if (option.get<0>() == num) {
529  DBG_MP << "\tsetting side " << option.get<0>() <<
530  "\tfaction: " << option.get<1>() << std::endl;
531 
532  side->set_controller_commandline(option.get<1>());
533  }
534  }
535  }
536 
537  // Set AI algorithm to RCA AI for all sides,
538  // then override if commandline option was given.
539  side->set_ai_algorithm("ai_default_rca");
540  if (cmdline_opts.multiplayer_algorithm) {
541  for (const mp_option& option : *cmdline_opts.multiplayer_algorithm) {
542 
543  if (option.get<0>() == num) {
544  DBG_MP << "\tsetting side " << option.get<0>() <<
545  "\tfaction: " << option.get<1>() << std::endl;
546 
547  side->set_ai_algorithm(option.get<1>());
548  }
549  }
550  }
551 
552  // Finally, resolve "random faction",
553  // "random gender" and "random message", if any remains unresolved.
554  side->resolve_random(rng);
555  } // end top-level loop
556 
557  update_and_send_diff(true);
558 
559  // Update sides with commandline parameters.
560  if (cmdline_opts.multiplayer_turns) {
561  DBG_MP << "\tsetting turns: " << *cmdline_opts.multiplayer_turns <<
562  std::endl;
563  scenario()["turns"] = *cmdline_opts.multiplayer_turns;
564  }
565 
566  for (config &side : scenario().child_range("side"))
567  {
568  if (cmdline_opts.multiplayer_ai_config) {
569  for (const mp_option& option : *cmdline_opts.multiplayer_ai_config) {
570 
571  if (option.get<0>() == side["side"].to_unsigned()) {
572  DBG_MP << "\tsetting side " << side["side"] <<
573  "\tai_config: " << option.get<1>() << std::endl;
574 
575  side["ai_config"] = option.get<1>();
576  }
577  }
578  }
579 
580  // Having hard-coded values here is undesirable,
581  // but that's how it is done in the MP lobby
582  // part of the code also.
583  // Should be replaced by settings/constants in both places
584  if (cmdline_opts.multiplayer_ignore_map_settings) {
585  side["gold"] = 100;
586  side["income"] = 1;
587  }
588 
589  typedef boost::tuple<unsigned int, std::string, std::string>
590  mp_parameter;
591 
592  if (cmdline_opts.multiplayer_parm) {
593  for (const mp_parameter& parameter : *cmdline_opts.multiplayer_parm) {
594 
595  if (parameter.get<0>() == side["side"].to_unsigned()) {
596  DBG_MP << "\tsetting side " << side["side"] << " " <<
597  parameter.get<1>() << ": " << parameter.get<2>() <<
598  std::endl;
599 
600  side[parameter.get<1>()] = parameter.get<2>();
601  }
602  }
603  }
604  }
605 
607 
608  // Build the gamestate object after updating the level
610  send_to_server(config("start_game"));
611 }
612 
613 std::pair<bool, bool> connect_engine::process_network_data(const config& data)
614 {
615  std::pair<bool, bool> result(std::make_pair(false, true));
616 
617  if (data.child("leave_game")) {
618  result.first = true;
619  return result;
620  }
621 
622  // A side has been dropped.
623  if (!data["side_drop"].empty()) {
624  unsigned side_drop = data["side_drop"].to_int() - 1;
625 
626  if (side_drop < side_engines_.size()) {
627  side_engine_ptr side_to_drop = side_engines_[side_drop];
628 
629  // Remove user, whose side was dropped.
630  connected_users_rw().erase(side_to_drop->player_id());
632 
633  side_to_drop->reset();
634 
636 
637  return result;
638  }
639  }
640 
641  // A player is connecting to the game.
642  if (!data["side"].empty()) {
643  unsigned side_taken = data["side"].to_int() - 1;
644 
645  // Checks if the connecting user has a valid and unique name.
646  const std::string name = data["name"];
647  if (name.empty()) {
648  config response;
649  response["failed"] = true;
650  send_to_server(response);
651 
652  ERR_CF << "ERROR: No username provided with the side." << std::endl;
653 
654  return result;
655  }
656 
657  if (connected_users().find(name) != connected_users().end()) {
658  // TODO: Seems like a needless limitation
659  // to only allow one side per player.
660  if (find_user_side_index_by_id(name) != -1) {
661  config response;
662  response["failed"] = true;
663  response["message"] = "The nickname '" + name +
664  "' is already in use.";
665  send_to_server(response);
666 
667  return result;
668  } else {
669  connected_users_rw().erase(name);
671  config observer_quit;
672  observer_quit.add_child("observer_quit")["name"] = name;
673  send_to_server(observer_quit);
674  }
675  }
676 
677  // Assigns this user to a side.
678  if (side_taken < side_engines_.size()) {
679  if (!side_engines_[side_taken]->available_for_user(name)) {
680  // This side is already taken.
681  // Try to reassing the player to a different position.
682  side_taken = 0;
684  if (s->available_for_user()) {
685  break;
686  }
687 
688  side_taken++;
689  }
690 
691  if (side_taken >= side_engines_.size()) {
692  config response;
693  response["failed"] = true;
694  send_to_server(response);
695 
696  config res;
697  config& kick = res.add_child("kick");
698  kick["username"] = data["name"];
699  send_to_server(res);
700 
702 
703  ERR_CF << "ERROR: Couldn't assign a side to '" <<
704  name << "'\n";
705 
706  return result;
707  }
708  }
709 
710  LOG_CF << "client has taken a valid position\n";
711 
712  import_user(data, false, side_taken);
714 
715  // Wait for them to choose faction if allowed.
716  side_engines_[side_taken]->
717  set_waiting_to_choose_status(side_engines_[side_taken]->
718  allow_changes());
719  LOG_MP << "waiting to choose status = " <<
720  side_engines_[side_taken]->allow_changes() << std::endl;
721  result.second = false;
722 
723  LOG_NW << "sent player data\n";
724  } else {
725  ERR_CF << "tried to take illegal side: " << side_taken << std::endl;
726 
727  config response;
728  response["failed"] = true;
729  send_to_server(response);
730  }
731  }
732 
733 
734  if (const config& change_faction = data.child("change_faction")) {
735  int side_taken = find_user_side_index_by_id(change_faction["name"]);
736  if (side_taken != -1 || !first_scenario_) {
737  import_user(change_faction, false, side_taken);
739  }
740  }
741 
742  if (const config& observer = data.child("observer")) {
743  import_user(observer, true);
745  }
746 
747  if (const config& observer = data.child("observer_quit")) {
748  const t_string& observer_name = observer["name"];
749  if (!observer_name.empty()) {
750  if ((connected_users().find(observer_name) != connected_users().end()) &&
751  (find_user_side_index_by_id(observer_name) != -1)) {
752 
753  connected_users_rw().erase(observer_name);
756  }
757  }
758  }
759 
760  return result;
761 }
762 
764 {
765  size_t i = 0;
766  for (side_engine_ptr side : side_engines_) {
767  if (side->player_id() == id) {
768  break;
769  }
770 
771  i++;
772  }
773 
774  if (i >= side_engines_.size()) {
775  return -1;
776  }
777 
778  return i;
779 }
780 
782 {
783  // Send initial information.
784  if (first_scenario_) {
786  ("create_game", config_of
787  ("name", params_.name)
788  ("password", params_.password)
789  )
790  );
792  } else {
793  send_to_server(config_of("update_game", config()));
794  config next_level;
795  next_level.add_child("store_next_scenario", level_);
796  send_to_server(next_level);
797  }
798 }
799 
801 {
802  // Add information about reserved sides to the level config.
803  // N.B. This information is needed only for a host player.
804  std::map<std::string, std::string> side_users =
805  utils::map_split(level_.child_or_empty("multiplayer")["side_users"]);
806  for (side_engine_ptr side : side_engines_) {
807  const std::string& save_id = side->save_id();
808  const std::string& player_id = side->player_id();
809  if (!save_id.empty() && !player_id.empty()) {
810  side_users[save_id] = player_id;
811  }
812  }
813  level_.child("multiplayer")["side_users"] = utils::join_map(side_users);
814 }
815 
817 {
818  std::map<std::string, std::string> side_users =
819  utils::map_split(level_.child("multiplayer")["side_users"]);
820  std::set<std::string> names;
821  for (side_engine_ptr side : side_engines_) {
822  const std::string& save_id = side->previous_save_id();
823  if (side_users.find(save_id) != side_users.end()) {
824  side->set_reserved_for(side_users[save_id]);
825 
826  if (side->controller() != CNTR_COMPUTER) {
827  side->set_controller(CNTR_RESERVED);
828  names.insert(side_users[save_id]);
829  }
830  side->update_controller_options();
831 
832  }
833  }
834  //Do this in an extra loop to make sure we import each user only once.
835  for (const std::string& name : names)
836  {
838  import_user(name, false);
839  }
840  }
841 }
842 
844 {
845  for (side_engine_ptr side : side_engines_) {
846  side->update_controller_options();
847  }
848 }
849 
850 
851 const std::set<std::string>& connect_engine::connected_users() const
852 {
853  if(campaign_info_) {
855  }
856  else {
857  static std::set<std::string> empty;
858  return empty;
859  }
860 }
861 std::set<std::string>& connect_engine::connected_users_rw()
862 {
863  assert(campaign_info_);
865 }
866 
867 side_engine::side_engine(const config& cfg, connect_engine& parent_engine,
868  const int index) :
869  cfg_(cfg),
870  parent_(parent_engine),
871  controller_(CNTR_NETWORK),
872  current_controller_index_(0),
873  controller_options_(),
874  allow_player_(cfg["allow_player"].to_bool(true)),
875  controller_lock_(cfg["controller_lock"].to_bool(
876  parent_.force_lock_settings_) && parent_.params_.use_map_settings),
877  index_(index),
878  team_(0),
879  color_(std::min(index, gamemap::MAX_PLAYERS - 1)),
880  gold_(cfg["gold"].to_int(100)),
881  income_(cfg["income"]),
882  reserved_for_(cfg["current_player"]),
883  player_id_(),
884  ai_algorithm_(),
885  chose_random_(cfg["chose_random"].to_bool(false)),
886  disallow_shuffle_(cfg["disallow_shuffle"].to_bool(false)),
887  flg_(parent_.era_factions_, cfg_, parent_.force_lock_settings_,
888  parent_.params_.use_map_settings, parent_.params_.saved_game),
889  allow_changes_(!parent_.params_.saved_game && !(flg_.choosable_factions().size() == 1 && flg_.choosable_leaders().size() == 1 && flg_.choosable_genders().size() == 1)),
890  waiting_to_choose_faction_(allow_changes_),
891  custom_color_()
892 {
893  // Save default attributes that could be overwirtten by the faction, so that correct faction lists would be
894  // initialized by flg_manager when the new side config is sent over network.
895  cfg_.add_child("default_faction", config_of
896  ("type", cfg_["type"])
897  ("gender", cfg_["gender"])
898  ("faction", cfg_["faction"])
899  ("recruit", cfg_["recruit"])
900  );
901 
902  // Check if this side should give its control to some other side.
903  const size_t side_cntr_index = cfg_["controller"].to_int(-1) - 1;
904  if (side_cntr_index < parent_.side_engines().size()) {
905  // Remove this attribute to avoid locking side
906  // to non-existing controller type.
907  cfg_.remove_attribute("controller");
908 
909  cfg_["previous_save_id"] = parent_.side_engines()[side_cntr_index]->previous_save_id();
910  ERR_MP << "contoller=<number> is deperecated\n";
911  }
912  if(!parent_.params_.saved_game && cfg_["save_id"].str().empty()) {
913  assert(cfg_["id"].empty()); // we already setted "save_id" to "id" if "id" existed.
914  cfg_["save_id"] = parent_.scenario()["id"].str() + "_" + std::to_string(index);
915  }
916 
918 
919  // Tweak the controllers.
920  if (parent_.state_.classification().campaign_type == game_classification::CAMPAIGN_TYPE::SCENARIO && cfg_["controller"].blank())
921  {
922  cfg_["controller"] = "ai";
923  }
924  if(cfg_["controller"] != "human" && cfg_["controller"] != "ai" && cfg_["controller"] != "null") {
925  //an invalid contoller type was specified. Remove it to prevent asertion failures later.
926  cfg_.remove_attribute("controller");
927  }
928 
929  if (cfg_["controller"] == "null") {
931  } else if (cfg_["controller"] == "ai") {
933  } else if (parent_.default_controller_ == CNTR_NETWORK && !reserved_for_.empty()) {
934  // Reserve a side for "current_player", unless the side
935  // is played by an AI.
937  } else if (allow_player_) {
939  } else {
940  // AI is the default.
942  }
943 
944  // Initialize team and color.
945  unsigned team_name_index = 0;
946  for (const std::string& name : parent_.team_names_) {
947  if (name == cfg["team_name"]) {
948  break;
949  }
950 
951  team_name_index++;
952  }
953  if (team_name_index >= parent_.team_names_.size()) {
954  assert(!parent_.team_names_.empty());
955  team_ = 0;
956  WRN_MP << "In side_engine constructor: Could not find my team_name " << cfg["team_name"] << " among the mp connect engine's list of team names. I am being assigned to the first team. This may indicate a bug!" << std::endl;
957  } else {
958  team_ = team_name_index;
959  }
960  if (!cfg["color"].empty()) {
961  if(cfg["color"].to_int()) {
962  color_ = cfg["color"].to_int() - 1;
963  }
964  else {
965  custom_color_ = cfg["color"].str();
966  color_ = 0;
967  }
968  }
969 
970  // Initialize ai algorithm.
971  if (const config& ai = cfg.child("ai")) {
972  ai_algorithm_ = ai["ai_algorithm"].str();
973  }
974 }
975 
977 {
978 }
979 
981 {
982  switch(controller_)
983  {
984  case CNTR_LOCAL:
985  return N_("Anonymous player");
986  case CNTR_COMPUTER:
987  if (allow_player_) {
989  } else {
990  return N_("Computer Player");
991  }
992  default:
993  return "";
994  }
995 }
996 
998 {
999  config res = cfg_;
1000 
1001  // If the user is allowed to change type, faction, leader etc,
1002  // then import their new values in the config.
1003  if (!parent_.params_.saved_game) {
1004  // Merge the faction data to res.
1005  config faction = flg_.current_faction();
1006  res["faction_name"] = faction["name"];
1007  res["faction"] = faction["id"];
1008  faction.remove_attributes("id", "name", "image", "gender", "type");
1009  res.append(faction);
1010  }
1011 
1012  if (!cfg_.has_attribute("side") || cfg_["side"].to_int() != index_ + 1) {
1013  res["side"] = index_ + 1;
1014  }
1015 
1016  res["controller"] = controller_names[controller_];
1017  // the hosts recieves the serversided controller tweaks after the start event, but
1018  // for mp sync it's very important that the controller types are correct
1019  // during the start/prestart event (otherwse random unit creation during prestart fails).
1020  res["is_local"] = player_id_ == preferences::login() || controller_ == CNTR_COMPUTER || controller_ == CNTR_LOCAL;
1021 
1022  std::string desc = user_description();
1023  if(!desc.empty()) {
1024  res["user_description"] = t_string(desc, "wesnoth");
1025  desc = vgettext(
1026  "$playername $side",
1027  boost::assign::map_list_of
1028  ("playername", _(desc.c_str()))
1029  ("side", res["side"].str())
1030  );
1031  } else if (!player_id_.empty()) {
1032  desc = player_id_;
1033  }
1034  if(res["name"].str().empty() && !desc.empty()) {
1035  //TODO: maybe we should add this in to the leaders config instead of the side config?
1036  res["name"] = desc;
1037  }
1038 
1039  assert(controller_ != CNTR_LAST);
1041  // Do not import default ai cfg otherwise - all is set by scenario config.
1042  res.add_child_at("ai", config_of("ai_algorithm", ai_algorithm_), 0);
1043  }
1044 
1045  if(controller_ == CNTR_EMPTY) {
1046  res["no_leader"] = true;
1047  }
1048 
1049  // Side's "current_player" is the player which is currently taken that side
1050  // or the one which is reserved to it.
1051  // "player_id" is the id of the client who controlls that side,
1052  // that always the host for Local players and AIs
1053  // any always empty for free/reserved sides or null controlled sides.
1054  // especialy you can use !res["player_id"].empty() to check whether a side is already taken.
1055  assert(!preferences::login().empty());
1056  if(controller_ == CNTR_LOCAL) {
1057  res["player_id"] = preferences::login();
1058  res["current_player"] = preferences::login();
1059  } else if(controller_ == CNTR_RESERVED) {
1060  res.remove_attribute("player_id");
1061  res["current_player"] = reserved_for_;
1062  } else if(controller_ == CNTR_COMPUTER) {
1063  //TODO what is the content of player_id_ here ?
1064  res["current_player"] = desc;
1065  res["player_id"] = preferences::login();
1066  } else if(!player_id_.empty()) {
1067  res["player_id"] = player_id_;
1068  res["current_player"] = player_id_;
1069  }
1070 
1071  res["allow_changes"] = allow_changes_;
1072  res["chose_random"] = chose_random_;
1073 
1074  if (!parent_.params_.saved_game) {
1075  // Find a config where a default leader is and set a new type
1076  // and gender values for it.
1077  config* leader = &res;
1078  if (flg_.default_leader_cfg() != nullptr) {
1079  for (config& side_unit : res.child_range("unit")) {
1080  if (*flg_.default_leader_cfg() == side_unit) {
1081  leader = &side_unit;
1082  if (flg_.current_leader() != (*leader)["type"]) {
1083  // If a new leader type was selected from carryover,
1084  // make sure that we reset the leader.
1085  std::string leader_id = (*leader)["id"];
1086  leader->clear();
1087  if (!leader_id.empty()) {
1088  (*leader)["id"] = leader_id;
1089  }
1090  }
1091 
1092  break;
1093  }
1094  }
1095  }
1096  (*leader)["type"] = flg_.current_leader();
1097  (*leader)["gender"] = flg_.current_gender();
1098 
1099  res["team_name"] = parent_.team_names_[team_];
1100  res["user_team_name"] = parent_.user_team_names_[team_];
1101  res["allow_player"] = allow_player_;
1102  res["color"] = get_color(color_);
1103  res["gold"] = gold_;
1104  res["income"] = income_;
1105  }
1106 
1108  config trimmed = cfg_;
1109 
1110  for (const std::string& attribute : attributes_to_trim) {
1111  trimmed.remove_attribute(attribute);
1112  }
1113 
1114  if (controller_ != CNTR_COMPUTER) {
1115  // Only override names for computer controlled players.
1116  trimmed.remove_attribute("user_description");
1117  }
1118 
1119  res.merge_with(trimmed);
1120  }
1121 
1122  return res;
1123 }
1124 
1126 {
1127  if (!allow_player_) {
1128  // Sides without players are always ready.
1129  return true;
1130  }
1131 
1132  if ((controller_ == CNTR_COMPUTER) ||
1133  (controller_ == CNTR_EMPTY) ||
1134  (controller_ == CNTR_LOCAL)) {
1135 
1136  return true;
1137  }
1138 
1139  if (available_for_user()) {
1140  // If controller_ == CNTR_NETWORK and player_id_.empty().
1141  return false;
1142  }
1143 
1144  if (controller_ == CNTR_NETWORK) {
1146  // The host is ready. A network player, who got a chance
1147  // to choose faction if allowed, is also ready.
1148  return true;
1149  }
1150  }
1151 
1152  return false;
1153 }
1154 
1156 {
1157  if (controller_ == CNTR_NETWORK && player_id_.empty()) {
1158  // Side is free and waiting for user.
1159  return true;
1160  }
1161  if (controller_ == CNTR_RESERVED && name.empty()) {
1162  // Side is still available to someone.
1163  return true;
1164  }
1165  if (controller_ == CNTR_RESERVED && reserved_for_ == name) {
1166  // Side is available only for the player with specific name.
1167  return true;
1168  }
1169 
1170  return false;
1171 }
1172 
1173 bool side_engine::swap_sides_on_drop_target(const unsigned drop_target) {
1174  assert(drop_target < parent_.side_engines_.size());
1175  side_engine& target = *parent_.side_engines_[drop_target];
1176 
1177  const std::string target_id = target.player_id_;
1178  const ng::controller target_controller = target.controller_;
1179  const std::string target_ai = target.ai_algorithm_;
1180 
1181  if ((controller_lock_ || target.controller_lock_) &&
1182  (controller_options_ != target.controller_options_)) {
1183 
1184  return false;
1185  }
1186 
1187  target.ai_algorithm_ = ai_algorithm_;
1188  if (player_id_.empty()) {
1189  target.player_id_.clear();
1190  target.set_controller(controller_);
1191  } else {
1192  target.place_user(player_id_);
1193  }
1194 
1195  ai_algorithm_ = target_ai;
1196  if (target_id.empty()) {
1197  player_id_.clear();
1198  set_controller(target_controller);
1199  } else {
1200  place_user(target_id);
1201  }
1202 
1203  return true;
1204 }
1205 
1206 void side_engine::resolve_random(rand_rng::mt_rng & rng, const std::vector<std::string> & avoid_faction_ids)
1207 {
1208  if (parent_.params_.saved_game) {
1209  return;
1210  }
1211 
1213 
1214  flg_.resolve_random(rng, avoid_faction_ids);
1215 
1216  LOG_MP << "side " << (index_ + 1) << ": faction=" <<
1217  (flg_.current_faction())["name"] << ", leader=" <<
1218  flg_.current_leader() << ", gender=" << flg_.current_gender() << "\n";
1219 }
1220 
1222 {
1223  player_id_.clear();
1226 
1227  if (!parent_.params_.saved_game) {
1229  }
1230 }
1231 
1233 {
1234  config data;
1235  data["name"] = name;
1236 
1237  place_user(data);
1238 }
1239 
1240 void side_engine::place_user(const config& data, bool contains_selection)
1241 {
1242  player_id_ = data["name"].str();
1244 
1245  if (data["change_faction"].to_bool() && contains_selection) {
1246  // Network user's data carry information about chosen
1247  // faction, leader and genders.
1248  flg_.set_current_faction(data["faction"].str());
1249  flg_.set_current_leader(data["leader"].str());
1250  flg_.set_current_gender(data["gender"].str());
1251  }
1252 
1254 }
1255 
1257 {
1258  controller_options_.clear();
1259 
1260  // Default options.
1261  if (parent_.campaign_info_) {
1262  add_controller_option(CNTR_NETWORK, _("Network Player"), "human");
1263  }
1264  add_controller_option(CNTR_LOCAL, _("Local Player"), "human");
1265  add_controller_option(CNTR_COMPUTER, _("Computer Player"), "ai");
1266  add_controller_option(CNTR_EMPTY, _("Empty"), "null");
1267 
1268  if (!reserved_for_.empty()) {
1269  add_controller_option(CNTR_RESERVED, _("Reserved"), "human");
1270  }
1271 
1272  // Connected users.
1273  add_controller_option(CNTR_LAST, _("--give--"), "human");
1274  for (const std::string& user : parent_.connected_users()) {
1276  }
1277 
1279 }
1280 
1282 {
1283  int i = 0;
1285  if (option.first == controller_) {
1287 
1288  if (player_id_.empty() || player_id_ == option.second) {
1289  // Stop searching if no user is assigned to a side
1290  // or the selected user is found.
1291  break;
1292  }
1293  }
1294 
1295  i++;
1296  }
1297 
1298  assert(current_controller_index_ < controller_options_.size());
1299 }
1300 
1301 bool side_engine::controller_changed(const int selection)
1302 {
1303  const ng::controller selected_cntr = controller_options_[selection].first;
1304  if (selected_cntr == CNTR_LAST) {
1305  return false;
1306  }
1307 
1308  // Check if user was selected. If so assign a side to him/her.
1309  // If not, make sure that no user is assigned to this side.
1310  if (selected_cntr == parent_.default_controller_ && selection != 0) {
1311  player_id_ = controller_options_[selection].second;
1313  } else {
1314  player_id_.clear();
1315  }
1316 
1317  set_controller(selected_cntr);
1318 
1319  return true;
1320 }
1321 
1323 {
1325 
1327 }
1328 
1330 {
1331  flg_.set_current_faction(faction_name);
1332 }
1333 
1335 {
1337 
1338  if (controller_name == "ai") {
1340  }
1341  if (controller_name == "null") {
1343  }
1344 
1345  player_id_.clear();
1346 }
1347 
1349  const std::string& name, const std::string& controller_value)
1350 {
1351  if (controller_lock_ && !cfg_["controller"].empty() &&
1352  cfg_["controller"] != controller_value) {
1353 
1354  return;
1355  }
1356 
1357  controller_options_.push_back(std::make_pair(controller, name));
1358 }
1359 
1360 std::vector<std::string> side_engine::get_colors() const
1361 {
1362  std::vector<std::string> res;
1363  for (int i = 0; i < num_colors(); ++i) {
1364  res.push_back(mp::get_color_string(get_color(i)));
1365  }
1366  return res;
1367 }
1368 
1370 {
1371  if(index == -1) {
1372  index = color();
1373  }
1374  if(!custom_color_.empty()) {
1375  if(index == 0) {
1376  return custom_color_;
1377  }
1378  index -= 1;
1379  }
1380  return std::to_string(index + 1);
1381 }
1382 
1384 {
1386 }
1387 
1388 } // end namespace ng
bool can_start_game() const
child_itors child_range(const std::string &key)
Definition: config.cpp:613
void remove_attribute(const std::string &key)
Definition: config.cpp:534
#define LOG_MP
std::string str() const
Definition: config.cpp:353
std::string join_map(const T &v, const std::string &major=",", const std::string &minor=":")
void save_reserved_sides_information()
boost::optional< std::vector< boost::tuple< unsigned int, std::string, std::string > > > multiplayer_parm
Non-empty if –parm was given on the command line. Vector of pairs (side number, parm name...
#define ERR_MP
void set_waiting_to_choose_status(bool status)
bool is_random_faction()
boost::optional< std::string > multiplayer_turns
Non-empty if –turns was given on the command line. Dependent on –multiplayer.
std::string era()
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:566
std::vector< std::string > player_teams_
void import_user(const std::string &name, const bool observer, int side_taken=-1)
boost::optional< std::vector< boost::tuple< unsigned int, std::string > > > multiplayer_algorithm
Non-empty if –algorithm was given on the command line. Vector of pairs (side number, value). Dependent on –multiplayer.
const mp_game_settings * mp_settings
Definition: resources.cpp:38
std::vector< side_engine_ptr > side_engines_
bool swap_sides_on_drop_target(const unsigned drop_target)
t_string t_str() const
Definition: config.cpp:358
void add_controller_option(ng::controller controller, const std::string &name, const std::string &controller_value)
uint32_t get_next_random()
Get a new random number.
Definition: mt_rng.cpp:63
config new_config() const
std::set< std::string > connected_players
players and observers
static lg::log_domain log_config("config")
config initial_level_config(saved_game &state)
void place_user(const std::string &name)
std::map< std::string, std::string > map_split(std::string const &val, char major, char minor, int flags, std::string const &default_value)
Splits a string based on two separators into a map.
#define LOG_NW
void set_faction_commandline(const std::string &faction_name)
#define WRN_MP
bool multiplayer_ignore_map_settings
True if –ignore-map-settings was given at the command line. Do not use map settings.
void set_controller_commandline(const std::string &controller_name)
bool receive_data(config &result)
std::string ai_algorithm_
std::string player_id_
static void add_mod_ai_from_config(config::const_child_itors configs)
void set_current_leader(const unsigned index)
SDL_Color color_
Definition: font.cpp:605
STL namespace.
#define ERR_CF
std::string get_color_string(int id)
void clear()
Definition: config.cpp:1055
void set_current_gender(const unsigned index)
const config & child_or_empty(const std::string &key) const
Returns the first child with the given key, or an empty config if there is none.
Definition: config.cpp:722
std::vector< std::string > user_team_names_
void level_to_gamestate(const config &level, saved_game &state)
const bool allow_changes_
GLint GLenum GLsizei GLint GLsizei const GLvoid * data
Definition: glew.h:1347
void clear_children(const std::string &key)
Definition: config.cpp:820
GLuint GLenum option
Definition: glew.h:2815
bool empty() const
Definition: config.cpp:1105
bool empty() const
Tests for an attribute that either was never set or was set to "".
Definition: config.cpp:375
std::vector< controller_option > controller_options_
bool ready_for_start() const
Variant for storing WML attributes.
Definition: config.hpp:223
void update_and_send_diff(bool update_time_of_day=false)
const std::set< std::string > & connected_users() const
bool sides_available() const
void send_data(const configr_of &request)
void merge_with(const config &c)
Merge config 'c' into this config, overwriting this config's values.
Definition: config.cpp:1350
std::string custom_color_
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:50
A small explanation about what's going on here: Each action has access to two game_info objects First...
Definition: actions.cpp:57
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
GLuint GLuint end
Definition: glew.h:1221
GLuint64EXT * result
Definition: glew.h:10727
GLuint id
Definition: glew.h:1647
std::vector< side_engine_ptr > & side_engines()
boost::optional< std::vector< boost::tuple< unsigned int, std::string > > > multiplayer_controller
Non-empty if –controller was given on the command line. Vector of pairs (side number, controller). Dependent on –multiplayer.
void send_to_server(const config &cfg) const
const std::string & current_gender() const
Definition: flg_manager.hpp:73
void set_controller(ng::controller controller)
void add_color_info(const config &v)
GLenum GLenum dst
Definition: glew.h:2392
Encapsulates the map of the game.
Definition: map.hpp:37
std::string user_description() const
#define LOG_CF
GLuint num
Definition: glew.h:2552
config & add_child(const std::string &key)
Definition: config.cpp:743
void start_game_commandline(const commandline_options &cmdline_opts)
std::vector< std::string > get_colors() const
const bool first_scenario_
ng::controller controller() const
bool available_for_user(const std::string &name="") const
twesnothd_connection & wesnothd_connection
ng::controller controller_
std::vector< const config * > era_factions_
GLuint const GLuint * names
Definition: glew.h:2552
mp_campaign_info * campaign_info_
const ng::controller default_controller_
#define DBG_MP
std::string login()
std::vector< std::string > get_children_to_swap()
GLuint res
Definition: glew.h:9258
void set_current_faction(const unsigned index)
config get_diff(const config &c) const
A function to get the differences between this object, and 'c', as another config object...
Definition: config.cpp:1134
void send_level_data() const
static void add_era_ai_from_config(const config &game_config)
void update_controller_options()
const config * default_leader_cfg() const
Definition: flg_manager.hpp:76
GLuint index
Definition: glew.h:1782
std::pair< child_iterator, child_iterator > child_itors
Definition: config.hpp:213
boost::optional< std::vector< boost::tuple< unsigned int, std::string > > > multiplayer_ai_config
Non-empty if –ai-config was given on the command line. Vector of pairs (side number, value). Dependent on –multiplayer.
std::string reserved_for_
int color() const
boost::optional< std::vector< boost::tuple< unsigned int, std::string > > > multiplayer_side
Non-empty if –side was given on the command line. Vector of pairs (side number, faction id)...
static lg::log_domain log_network("network")
size_t i
Definition: function.cpp:1057
connect_engine(saved_game &state, const bool first_scenario, mp_campaign_info *campaign_info)
bool receive_from_server(config &dst) const
int num_colors() const
#define N_(String)
Definition: gettext.hpp:90
void update_current_controller_index()
const config & current_faction() const
Definition: flg_manager.hpp:69
unsigned current_controller_index_
connect_engine & parent_
std::string observer
Definition: game_config.cpp:84
std::string to_serialized() const
Definition: tstring.hpp:136
std::string vgettext(const char *msgid, const utils::string_map &symbols)
GLuint const GLchar * name
Definition: glew.h:1782
std::pair< ng::controller, std::string > controller_option
const std::string & current_leader() const
Definition: flg_manager.hpp:71
int find_user_side_index_by_id(const std::string &id) const
GLsizeiptr size
Definition: glew.h:1649
std::set< std::string > & connected_users_rw()
bool has_attribute(const std::string &key) const
Definition: config.cpp:514
bool find(E event, F functor)
Tests whether an event handler is available.
game_classification & classification()
Definition: saved_game.hpp:54
Managing the AIs configuration - headers.
config & child(const std::string &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:658
const bool controller_lock_
Standard logging facilities (interface).
bool controller_changed(const int selection)
void set_side_children(std::multimap< std::string, config > children)
static lg::log_domain log_mp_connect_engine("mp/connect/engine")
const mp_game_settings & params_
std::string get_color(int index=-1) const
void remove_attributes(T...keys)
Definition: config.hpp:571
void resolve_random(rand_rng::mt_rng &rng, const std::vector< std::string > &avoid)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
const bool allow_player_
void resolve_random(rand_rng::mt_rng &rng, const std::vector< std::string > &avoid_faction_ids=std::vector< std::string >())
GLdouble s
Definition: glew.h:1358
mp_game_settings & mp_settings()
Multiplayer parameters for this game.
Definition: saved_game.hpp:58
friend class side_engine
static const config & get_ai_config_for(const std::string &id)
Return the config for a specified ai.
std::vector< std::string > team_names_
side_engine(const config &cfg, connect_engine &parent_engine, const int index)
GLsizei const GLcharARB ** string
Definition: glew.h:4503
std::multimap< std::string, config > get_side_children()
bool empty() const
Definition: tstring.hpp:166
config & add_child_at(const std::string &key, const config &val, unsigned index)
Definition: config.cpp:773
GLenum target
Definition: glew.h:5190
std::pair< bool, bool > process_network_data(const config &data)
void update_side_controller_options()