The Battle for Wesnoth  1.19.9+dev
menu_events.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2025
3  by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 /**
18  * @file
19  * Operations activated from menus/hotkeys while playing a game.
20  * E.g. Unitlist, status_table, save_game, save_map, chat, show_help, etc.
21  */
22 
23 #include "menu_events.hpp"
24 
25 #include "actions/create.hpp"
26 #include "actions/move.hpp"
27 #include "actions/undo.hpp"
28 #include "ai/manager.hpp"
29 #include "chat_command_handler.hpp"
30 #include "color.hpp"
31 #include "display_chat_manager.hpp"
32 #include "font/standard_colors.hpp"
33 #include "formula/string_utils.hpp"
34 #include "game_board.hpp"
35 #include "game_config_manager.hpp"
36 #include "game_end_exceptions.hpp"
38 #include "game_state.hpp"
39 #include "gettext.hpp"
40 #include "gui/dialogs/chat_log.hpp"
47 #include "gui/dialogs/message.hpp"
55 #include "gui/widgets/retval.hpp"
56 #include "help/help.hpp"
57 #include "log.hpp"
58 #include "map/label.hpp"
59 #include "map/location.hpp"
60 #include "map/map.hpp"
61 #include "map_command_handler.hpp"
62 #include "mouse_events.hpp"
63 #include "play_controller.hpp"
66 #include "replay.hpp"
67 #include "replay_controller.hpp"
68 #include "replay_helper.hpp"
69 #include "resources.hpp"
70 #include "savegame.hpp"
71 #include "serialization/chrono.hpp"
72 #include "serialization/markup.hpp"
75 #include "synced_context.hpp"
76 #include "units/helper.hpp"
77 #include "units/ptr.hpp"
78 #include "units/unit.hpp"
79 #include "units/types.hpp"
80 #include "whiteboard/manager.hpp"
81 #include "sound.hpp"
82 
83 static lg::log_domain log_engine("engine");
84 #define ERR_NG LOG_STREAM(err, log_engine)
85 #define LOG_NG LOG_STREAM(info, log_engine)
86 
87 // This file acts as launcher for many gui2 dialogs
88 using namespace gui2::dialogs;
89 
90 namespace events
91 {
92 menu_handler::menu_handler(game_display* gui, play_controller& pc)
93  : gui_(gui)
94  , pc_(pc)
95  , game_config_(game_config_manager::get()->game_config())
96  , textbox_info_()
97  , last_search_()
98  , last_search_hit_()
99 {
100 }
101 
103 {
104 }
105 
107 {
108  return pc_.gamestate();
109 }
110 
112 {
113  return gamestate().gamedata_;
114 }
115 
117 {
118  return gamestate().board_;
119 }
120 
122 {
123  return textbox_info_;
124 }
125 
127 {
128  if(!gamestate().lua_kernel_) {
129  return;
130  }
131 
134 }
135 
137 {
138  statistics_dialog::display(pc_.statistics(), board().get_team(side_num));
139 }
140 
142 {
143  std::vector<unit_const_ptr> unit_list;
144 
145  const unit_map& units = gui_->context().units();
146  for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
147  if(i->side() != gui_->viewing_team().side()) {
148  continue;
149  }
150  unit_list.push_back(i.get_shared_ptr());
151  }
152 
153  const auto& unit_dlg = units_dialog::build_unit_list_dialog(unit_list);
154 
155  if (unit_dlg->show() && unit_dlg->is_selected()) {
156  const map_location& loc = unit_list[unit_dlg->get_selected_index()]->get_location();
158  gui_->select_hex(loc);
159  }
160 }
161 
163 {
164  int selected_side;
165 
166  if(game_stats::execute(board(), gui_->viewing_team(), selected_side)) {
167  gui_->scroll_to_leader(selected_side);
168  }
169 }
170 
172 {
173  const std::string input_name = filesystem::get_legacy_editor_dir() + "/maps/";
174 
175  file_dialog dlg;
176 
177  dlg.set_title(_("Save Map As"))
178  .set_save_mode(true)
179  .set_path(input_name)
181 
182  if(!dlg.show()) {
183  return;
184  }
185 
186  try {
188  gui2::show_transient_message("", _("Map saved."));
189  } catch(const filesystem::io_exception& e) {
190  utils::string_map symbols;
191  symbols["msg"] = e.what();
192  const std::string msg = VGETTEXT("Could not save the map: $msg", symbols);
194  }
195 }
196 
198 {
199  // Causes MSVC ambiguity
200  gui2::dialogs::preferences_dialog::display();
201  // Needed after changing fullscreen/windowed mode or display resolution
202  gui_->queue_rerender();
203 }
204 
206 {
207  config c;
208  c["name"] = "prototype of chat log";
209  chat_log::display(vconfig(c), *resources::recorder);
210 }
211 
213 {
214  help::show_help();
215 }
216 
218 {
220  ? board().is_observer()
221  ? _("Send to observers only")
222  : _("Send to allies only")
223  : "", prefs::get().message_private(), *gui_);
224 }
225 
227 {
229  speak();
230 }
231 
233 {
235  speak();
236 }
237 
239 {
240  if(board().is_observer()) {
241  return !gui_->observers().empty();
242  }
243 
244  for(const team& t : pc_.get_teams()) {
245  if(gui_->viewing_team().side() == t.side()) {
246  continue; // Can't be friends with yourself
247  }
248 
249  if(gui_->viewing_team().team_name() == t.team_name() && t.is_network()) {
250  return true;
251  }
252  }
253 
254  return false;
255 }
256 
257 void menu_handler::recruit(int side_num, const map_location& last_hex)
258 {
259  std::map<const unit_type*, t_string> err_msgs_map;
260  std::vector<const unit_type*> recruit_list;
261  std::set<std::string> recruits = actions::get_recruits(side_num, last_hex);
262  std::vector<t_string> unknown_units;
263  team& current_team = board().get_team(side_num);
264 
265  for(const auto& recruit : recruits) {
267  if(!type) {
268  ERR_NG << "could not find unit '" << recruit << "'";
269  unknown_units.emplace_back(recruit);
270  continue;
271  }
272 
273  map_location ignored;
274  map_location recruit_hex = last_hex;
275  t_string err_msg = unit_helper::recruit_message(type->id(), recruit_hex, ignored, current_team);
276  if (!err_msg.empty()) {
277  err_msgs_map[type] = err_msg;
278  }
279  recruit_list.push_back(type);
280  }
281 
282  if(!unknown_units.empty()) {
283  auto unknown_ids = utils::format_conjunct_list("", unknown_units);
284  // TRANSLATORS: An error that should never happen, might happen when loading an old savegame. If there are
285  // any units that the player can recruit then their standard recruitment dialog will be shown after this
286  // error message, otherwise they'll get the "You have no units available to recruit." error after this one.
287  const auto message = VNGETTEXT("Error: there’s an unknown unit type on your recruit list: $unknown_ids",
288  "Error: there are several unknown unit types on your recruit list: $unknown_ids",
289  unknown_units.size(),
290  utils::string_map { { "unknown_ids", unknown_ids }});
292  }
293 
294  if(recruit_list.empty()) {
295  gui2::show_transient_message("", _("You have no units available to recruit."));
296  return;
297  }
298 
299  const auto& dlg = units_dialog::build_recruit_dialog(recruit_list, err_msgs_map, current_team);
300 
301  if(dlg->show() && dlg->is_selected()) {
302  const auto& type = recruit_list[dlg->get_selected_index()];
303  map_location recruit_hex = last_hex;
304  do_recruit(type->id(), side_num, recruit_hex);
305  }
306 }
307 
308 void menu_handler::repeat_recruit(int side_num, const map_location& last_hex)
309 {
310  const std::string& last_recruit = board().get_team(side_num).last_recruit();
311  if(!last_recruit.empty()) {
312  map_location recruit_hex = last_hex;
313  do_recruit(last_recruit, side_num, recruit_hex);
314  }
315 }
316 
317 bool menu_handler::do_recruit(const std::string& name, int side_num, map_location& loc)
318 {
319  map_location recruited_from = map_location::null_location();
320  team& current_team = board().get_team(side_num);
321  const auto& res = unit_helper::recruit_message(name, loc, recruited_from, current_team);
322 
323  if(res.empty() && (!pc_.get_whiteboard() || !pc_.get_whiteboard()->save_recruit(name, side_num, loc))) {
324  // MP_COUNTDOWN grant time bonus for recruiting
325  current_team.set_action_bonus_count(1 + current_team.action_bonus_count());
326 
327  // Do the recruiting.
328  synced_context::run_and_throw("recruit", replay_helper::get_recruit(name, loc, recruited_from));
329  return true;
330  } else if(res.empty()) {
331  return false;
332  } else {
334  return false;
335  }
336 
337 }
338 
339 void menu_handler::recall(int side_num, const map_location& last_hex)
340 {
341  if(pc_.get_disallow_recall()) {
342  gui2::show_transient_message("", _("You are separated from your soldiers and may not recall them."));
343  return;
344  }
345 
346  team& current_team = board().get_team(side_num);
347 
348  std::vector<unit_const_ptr> recall_list_team;
349  {
350  wb::future_map future; // ensures recall list has planned recalls removed
351  recall_list_team = actions::get_recalls(side_num, last_hex);
352  }
353 
354  DBG_WB << "menu_handler::recall: Contents of wb-modified recall list:";
355  for(const unit_const_ptr& unit : recall_list_team) {
356  DBG_WB << unit->name() << " [" << unit->id() << "]";
357  }
358 
359  if(current_team.recall_list().empty()) {
361  _("There are no troops available to recall.\n(You must have veteran survivors from a previous scenario.)"));
362  return;
363  }
364 
365  if(recall_list_team.empty()) {
366  gui2::show_transient_message("", _("You currently can’t recall at the highlighted location."));
367  return;
368  }
369 
370  const auto& dlg = units_dialog::build_recall_dialog(recall_list_team, current_team);
371 
372  if(!dlg->show() || !dlg->is_selected()) {
373  return;
374  }
375 
376  int res = dlg->get_selected_index();
377 
378  int unit_cost = current_team.recall_cost();
379  const unit_const_ptr sel_unit = recall_list_team[res];
380 
381  // we need to check if unit has a specific recall cost
382  // if it does we use it elsewise we use the team.recall_cost()
383  // the magic number -1 is what it gets set to if the unit doesn't
384  // have a special recall_cost of its own.
385  if(sel_unit->recall_cost() > -1) {
386  unit_cost = sel_unit->recall_cost();
387  }
388 
389  int wb_gold = pc_.get_whiteboard() ? pc_.get_whiteboard()->get_spent_gold_for(side_num) : 0;
390 
391  if(current_team.gold() - wb_gold < unit_cost) {
392  utils::string_map i18n_symbols;
393  i18n_symbols["cost"] = std::to_string(unit_cost);
394  std::string msg = VNGETTEXT("You must have at least 1 gold piece to recall a unit.",
395  "You must have at least $cost gold pieces to recall this unit.", unit_cost, i18n_symbols);
397  return;
398  }
399 
400  LOG_NG << "recall index: " << res;
401  const events::command_disabler disable_commands;
402 
403  map_location recall_location = last_hex;
405  std::string err;
406  {
408  future; // future unit map removes invisible units from map, don't do this outside of planning mode
409  err = actions::find_recall_location(side_num, recall_location, recall_from, *sel_unit);
410  } // end planned unit map scope
411 
412  if(!err.empty()) {
414  return;
415  }
416 
417  if(!pc_.get_whiteboard()
418  || !pc_.get_whiteboard()->save_recall(*sel_unit, side_num, recall_location)) {
419  bool success = synced_context::run_and_throw("recall",
420  replay_helper::get_recall(sel_unit->id(), recall_location, recall_from));
421 
422  if(!success) {
423  ERR_NG << "menu_handler::recall(): Unit does not exist in the recall list.";
424  }
425  }
426 }
427 
428 // Highlights squares that an enemy could move to on their turn, showing how many can reach each square.
429 void menu_handler::show_enemy_moves(bool ignore_units, int side_num)
430 {
431  wb::future_map future; // use unit positions as if all planned actions were executed
432 
433  mouse_handler& mh = pc_.get_mouse_handler_base();
434  const map_location& hex_under_mouse = mh.hovered_hex();
435 
436  gui_->unhighlight_reach();
437 
438  // Compute enemy movement positions
439  for(auto& u : pc_.get_units()) {
440  bool invisible = u.invisible(u.get_location());
441 
442  if(board().get_team(side_num).is_enemy(u.side()) && !gui_->fogged(u.get_location()) && !u.incapacitated()
443  && !invisible) {
444  const unit_movement_resetter move_reset(u);
445  const pathfind::paths& path
446  = pathfind::paths(u, false, true, gui_->viewing_team(), 0, false, ignore_units);
447 
448  gui_->highlight_another_reach(path, hex_under_mouse);
449  }
450 
451  // Need to recompute ellipses for highlighted enemy units
452  gui_->invalidate(u.get_location());
453  }
454 
455  // Find possible unit (no matter whether friend or foe) under the
456  // mouse cursor.
457  const bool selected_hex_has_unit = mh.hex_hosts_unit(hex_under_mouse);
458 
459  if(selected_hex_has_unit) {
460  // At this point, a single pixel move would remove the enemy
461  // [best possible] movements hex tiles highlights, so some
462  // prevention on normal unit mouseover movement highlight
463  // has to be toggled temporarily.
465  }
466 }
467 
468 void menu_handler::toggle_shroud_updates(int side_num)
469 {
470  team& current_team = board().get_team(side_num);
471  bool auto_shroud = current_team.auto_shroud_updates();
472  // If we're turning automatic shroud updates on, then commit all moves
473  if(!auto_shroud) {
474  update_shroud_now(side_num);
475  }
476 
477  // Toggle the setting and record this.
479 }
480 
481 void menu_handler::update_shroud_now(int /* side_num */)
482 {
484 }
485 
486 // Helpers for menu_handler::end_turn()
487 namespace
488 {
489 /** Returns true if @a side_num has at least one living unit. */
490 bool units_alive(int side_num, const unit_map& units)
491 {
492  for(auto& unit : units) {
493  if(unit.side() == side_num) {
494  return true;
495  }
496  }
497  return false;
498 }
499 
500 /** Returns true if @a side_num has at least one unit that can still move. */
501 bool partmoved_units(
502  int side_num, const unit_map& units, const game_board& board, const std::shared_ptr<wb::manager>& whiteb)
503 {
504  for(auto& unit : units) {
505  if(unit.side() == side_num) {
506  // @todo whiteboard should take into consideration units that have
507  // a planned move but can still plan more movement in the same turn
508  if(board.unit_can_move(unit) && !unit.user_end_turn() && (!whiteb || !whiteb->unit_has_actions(&unit)))
509  return true;
510  }
511  }
512  return false;
513 }
514 
515 /**
516  * Returns true if @a side_num has at least one unit that (can but) has not moved.
517  */
518 bool unmoved_units(
519  int side_num, const unit_map& units, const game_board& board, const std::shared_ptr<wb::manager>& whiteb)
520 {
521  for(auto& unit : units) {
522  if(unit.side() == side_num) {
523  if(board.unit_can_move(unit) && !unit.has_moved() && !unit.user_end_turn()
524  && (!whiteb || !whiteb->unit_has_actions(&unit))) {
525  return true;
526  }
527  }
528  }
529  return false;
530 }
531 
532 } // end anon namespace
533 
534 bool menu_handler::end_turn(int side_num)
535 {
536  if(!gamedata().allow_end_turn()) {
537  t_string reason = gamedata().cannot_end_turn_reason();
538  if(reason.empty()) {
539  reason = _("You cannot end your turn yet!");
540  }
541  gui2::show_transient_message("", reason);
542  return false;
543  }
544 
545  std::size_t team_num = static_cast<std::size_t>(side_num - 1);
546  if(team_num < pc_.get_teams().size() && pc_.get_teams()[team_num].no_turn_confirmation()) {
547  // Skip the confirmations that follow.
548  }
549  // Ask for confirmation if the player hasn't made any moves.
550  else if(prefs::get().confirm_no_moves() && !pc_.get_undo_stack().player_acted()
551  && (!pc_.get_whiteboard() || !pc_.get_whiteboard()->current_side_has_actions())
552  && units_alive(side_num, pc_.get_units())) {
553  const int res = gui2::show_message("",
554  _("You have not started your turn yet. Do you really want to end your turn?"),
555  message::yes_no_buttons);
556  if(res == gui2::retval::CANCEL) {
557  return false;
558  }
559  }
560  // Ask for confirmation if units still have some movement left.
561  else if(prefs::get().yellow_confirm() && partmoved_units(side_num, pc_.get_units(), board(), pc_.get_whiteboard())) {
562  const int res = gui2::show_message("",
563  _("Some units have movement left. Do you really want to end your turn?"),
564  message::yes_no_buttons);
565  if(res == gui2::retval::CANCEL) {
566  return false;
567  }
568  }
569  // Ask for confirmation if units still have all movement left.
570  else if(prefs::get().green_confirm() && unmoved_units(side_num, pc_.get_units(), board(), pc_.get_whiteboard())) {
571  const int res = gui2::show_message("",
572  _("Some units have not moved. Do you really want to end your turn?"),
573  message::yes_no_buttons);
574  if(res == gui2::retval::CANCEL) {
575  return false;
576  }
577  }
578 
579  // Auto-execute remaining whiteboard planned actions
580  // Only finish turn if they all execute successfully, i.e. no ambush, etc.
581  if(pc_.get_whiteboard() && !pc_.get_whiteboard()->allow_end_turn()) {
582  return false;
583  }
584 
585  return true;
586 }
587 
588 void menu_handler::goto_leader(int side_num)
589 {
590  unit_map::const_iterator i = pc_.get_units().find_leader(side_num);
591  if(i != pc_.get_units().end() && i->is_visible_to_team(gui_->viewing_team(), false)) {
592  gui_->scroll_to_tile(i->get_location(), game_display::WARP);
593  }
594 }
595 
596 void menu_handler::unit_description()
597 {
598  const unit_map::const_iterator un = current_unit();
599  if(un != pc_.get_units().end()) {
601  }
602 }
603 
604 void menu_handler::terrain_description(mouse_handler& mousehandler)
605 {
606  const map_location& loc = mousehandler.get_last_hex();
607  if(pc_.get_map().on_board(loc) == false || gui_->shrouded(loc)) {
608  return;
609  }
610 
611  const terrain_type& type = pc_.get_map().get_terrain_info(loc);
612  // const terrain_type& info = board().pc_.get_map().get_terrain_info(terrain);
614 }
615 
616 void menu_handler::rename_unit()
617 {
618  const unit_map::iterator un = current_unit();
619  if(un == pc_.get_units().end() || gui_->viewing_team().side() != un->side()) {
620  return;
621  }
622 
623  if(un->unrenamable()) {
624  return;
625  }
626 
627  std::string name = un->name();
628  const std::string title(_("Rename Unit"));
629  const std::string label(_("Name:"));
630 
631  if(edit_text::execute(title, label, name)) {
632  resources::recorder->add_rename(name, un->get_location());
633  un->rename(name);
634  gui_->invalidate_unit();
635  }
636 }
637 
638 unit_map::iterator menu_handler::current_unit()
639 {
640  const mouse_handler& mousehandler = pc_.get_mouse_handler_base();
641  const bool see_all = gui_->show_everything() || (pc_.is_replay() && pc_.get_replay_controller()->see_all());
642 
643  unit_map::iterator res = board().find_visible_unit(mousehandler.get_last_hex(), gui_->viewing_team(), see_all);
644  if(res != pc_.get_units().end()) {
645  return res;
646  }
647 
648  return board().find_visible_unit(mousehandler.get_selected_hex(), gui_->viewing_team(), see_all);
649 }
650 
651 // Helpers for create_unit()
652 namespace
653 {
654 /** Allows a function to return both a type and a gender. */
655 typedef std::tuple<const unit_type*, unit_race::GENDER, std::string> type_gender_variation;
656 
657 /**
658  * Allows the user to select a type of unit, using GUI2.
659  * (Intended for use when a unit is created in debug mode via hotkey or
660  * context menu.)
661  * @returns the selected type and gender. If this is canceled, the
662  * returned type is nullptr.
663  */
664 type_gender_variation choose_unit()
665 {
666  const auto& types_list = unit_types.types_list();
667  const auto& create_dlg = units_dialog::build_create_dialog(types_list);
668 
669  if (!create_dlg->show() || !create_dlg->is_selected()) {
670  ERR_NG << "Create unit dialog returned nonexistent or unusable unit_type id.";
671  return type_gender_variation(nullptr, unit_race::NUM_GENDERS, "");
672  }
673 
674  const unit_type* ut = types_list[create_dlg->get_selected_index()];
675 
676  unit_race::GENDER gender = create_dlg->gender();
677  return type_gender_variation(ut, gender, create_dlg->variation());
678 }
679 
680 /**
681  * Creates a unit and places it on the board.
682  * (Intended for use with any units created via debug mode.)
683  */
684 void create_and_place(
685  game_display&,
686  const gamemap&,
687  unit_map&,
688  const map_location& loc,
689  const unit_type& u_type,
691  const std::string& variation = "")
692 {
693  synced_context::run_and_throw("debug_create_unit",
694  config {
695  "x", loc.wml_x(),
696  "y", loc.wml_y(),
697  "type", u_type.id(),
698  "gender", gender_string(gender),
699  "variation", variation,
700  }
701  );
702 }
703 
704 } // Anonymous namespace
705 
706 /**
707  * Creates a unit (in debug mode via hotkey or context menu).
708  */
709 void menu_handler::create_unit(mouse_handler& mousehandler)
710 {
711  // Save the current mouse location before popping up the choice menu (which
712  // gives time for the mouse to move, changing the location).
713  const map_location destination = mousehandler.get_last_hex();
714  assert(gui_ != nullptr);
715 
716  // Let the user select the kind of unit to create.
717  if(const auto& [type, gender, variation] = choose_unit(); type != nullptr) {
718  // Make it so.
719  create_and_place(*gui_, pc_.get_map(), pc_.get_units(), destination, *type, gender, variation);
720  }
721 }
722 
723 void menu_handler::change_side(mouse_handler& mousehandler)
724 {
725  const map_location& loc = mousehandler.get_last_hex();
726  const unit_map::iterator i = pc_.get_units().find(loc);
727  if(i == pc_.get_units().end()) {
728  if(!pc_.get_map().is_village(loc)) {
729  return;
730  }
731 
732  // village_owner returns 0 for free village, so team 0 will get it
733  int team = board().village_owner(loc);
734  // team is 0-based so team=team::nteams() is not a team
735  // but this will make get_village free it
736  if(team > static_cast<int>(pc_.get_teams().size())) {
737  team = 0;
738  }
740  } else {
741  int side = i->side();
742  ++side;
743  if(side > static_cast<int>(pc_.get_teams().size())) {
744  side = 1;
745  }
746  i->set_side(side);
747 
748  if(pc_.get_map().is_village(loc)) {
749  actions::get_village(loc, side);
750  }
751  }
752 }
753 
754 void menu_handler::kill_unit(mouse_handler& mousehandler)
755 {
756  const map_location loc = mousehandler.get_last_hex();
757  synced_context::run_and_throw("debug_kill", config {"x", loc.wml_x(), "y", loc.wml_y()});
758 }
759 
760 void menu_handler::label_terrain(mouse_handler& mousehandler, bool team_only)
761 {
762  const map_location& loc = mousehandler.get_last_hex();
763  if(pc_.get_map().on_board(loc) == false) {
764  return;
765  }
766 
767  const terrain_label* old_label = gui_->labels().get_label(loc);
768  std::string label = old_label ? old_label->text() : "";
769 
770  if(edit_label::execute(label, team_only)) {
771  std::string team_name;
772  color_t color = font::LABEL_COLOR;
773 
774  if(team_only) {
775  team_name = gui_->labels().team_name();
776  } else {
777  color = team::get_side_color(gui_->viewing_team().side());
778  }
779  const terrain_label* res = gui_->labels().set_label(loc, label, gui_->viewing_team_index(), team_name, color);
780  if(res) {
782  }
783  }
784 }
785 
786 void menu_handler::clear_labels()
787 {
788  if(!board().is_observer()) {
789  const int res = gui2::show_message(
790  _("Clear Labels"),
791  _("Are you sure you want to clear map labels?"),
792  message::yes_no_buttons
793  );
794 
795  if(res == gui2::retval::OK) {
796  std::string viewing_team = gui_->viewing_team().team_name();
797  gui_->labels().clear(viewing_team, false);
798  resources::recorder->clear_labels(viewing_team, false);
799  }
800  }
801 }
802 
803 void menu_handler::label_settings()
804 {
805  if(label_settings::execute(board())) {
806  gui_->labels().recalculate_labels();
807  }
808 }
809 
810 void menu_handler::continue_move(mouse_handler& mousehandler, int side_num)
811 {
812  unit_map::iterator i = current_unit();
813  if(i == pc_.get_units().end() || !i->move_interrupted()) {
814  i = pc_.get_units().find(mousehandler.get_selected_hex());
815  if(i == pc_.get_units().end() || !i->move_interrupted()) {
816  return;
817  }
818  }
819  move_unit_to_loc(i, i->get_interrupted_move(), true, side_num, mousehandler);
820 }
821 
822 void menu_handler::move_unit_to_loc(
823  const unit_map::iterator& ui,
824  const map_location& target,
825  bool continue_move,
826  int side_num,
827  mouse_handler& mousehandler)
828 {
829  assert(ui != pc_.get_units().end());
830 
831  pathfind::marked_route route = mousehandler.get_route(&*ui, target, board().get_team(side_num));
832 
833  if(route.steps.empty()) {
834  return;
835  }
836 
837  assert(route.steps.front() == ui->get_location());
838 
839  gui_->set_route(&route);
840  gui_->unhighlight_reach();
841 
842  {
843  LOG_NG << "move_unit_to_loc " << route.steps.front() << " to " << route.steps.back();
844  actions::move_unit_and_record(route.steps, continue_move);
845  }
846 
847  mousehandler.deselect_hex();
848  gui_->set_route(nullptr);
849  gui_->invalidate_game_status();
850 }
851 
852 void menu_handler::execute_gotos(mouse_handler& mousehandler, int side)
853 {
854  // we will loop on all gotos and try to fully move a maximum of them,
855  // but we want to avoid multiple blocking of the same unit,
856  // so, if possible, it's better to first wait that the blocker move
857 
858  bool wait_blocker_move = true;
859  std::set<map_location> fully_moved;
860 
861  bool change = false;
862  bool blocked_unit = false;
863  do {
864  change = false;
865  blocked_unit = false;
866  for(auto& unit : pc_.get_units()) {
867  if(unit.side() != side || unit.movement_left() == 0) {
868  continue;
869  }
870 
871  const map_location& current_loc = unit.get_location();
872  const map_location& goto_loc = unit.get_goto();
873 
874  if(goto_loc == current_loc) {
876  continue;
877  }
878 
879  if(!pc_.get_map().on_board(goto_loc)) {
880  continue;
881  }
882 
883  // avoid pathfinding calls for finished units
884  if(fully_moved.count(current_loc)) {
885  continue;
886  }
887 
888  pathfind::marked_route route = mousehandler.get_route(&unit, goto_loc, board().get_team(side));
889 
890  if(route.steps.size() <= 1) { // invalid path
891  fully_moved.insert(current_loc);
892  continue;
893  }
894 
895  // look where we will stop this turn (turn_1 waypoint or goto)
896  map_location next_stop = goto_loc;
897  pathfind::marked_route::mark_map::const_iterator w = route.marks.begin();
898  for(; w != route.marks.end(); ++w) {
899  if(w->second.turns == 1) {
900  next_stop = w->first;
901  break;
902  }
903  }
904 
905  if(next_stop == current_loc) {
906  fully_moved.insert(current_loc);
907  continue;
908  }
909 
910  // we delay each blocked move because some other change
911  // may open a another not blocked path
912  if(pc_.get_units().count(next_stop)) {
913  blocked_unit = true;
914  if(wait_blocker_move)
915  continue;
916  }
917 
918  gui_->set_route(&route);
919 
920  {
921  LOG_NG << "execute goto from " << route.steps.front() << " to " << route.steps.back();
922  int moves = actions::move_unit_and_record(route.steps);
923  change = moves > 0;
924  }
925 
926  if(change) {
927  // something changed, resume waiting blocker (maybe one can move now)
928  wait_blocker_move = true;
929  }
930  }
931 
932  if(!change && wait_blocker_move) {
933  // no change when waiting, stop waiting and retry
934  wait_blocker_move = false;
935  change = true;
936  }
937  } while(change && blocked_unit);
938 
939  // erase the footsteps after movement
940  gui_->set_route(nullptr);
941  gui_->invalidate_game_status();
942 }
943 
944 void menu_handler::toggle_ellipses()
945 {
946  prefs::get().set_show_side_colors(!prefs::get().show_side_colors());
947  gui_->invalidate_all(); // TODO can fewer tiles be invalidated?
948 }
949 
950 void menu_handler::toggle_grid()
951 {
952  prefs::get().set_grid(!prefs::get().grid());
953  gui_->invalidate_all();
954 }
955 
956 void menu_handler::unit_hold_position(mouse_handler& mousehandler, int side_num)
957 {
958  const unit_map::iterator un = pc_.get_units().find(mousehandler.get_selected_hex());
959  if(un != pc_.get_units().end() && un->side() == side_num && un->movement_left() >= 0) {
960  un->toggle_hold_position();
961  gui_->invalidate(mousehandler.get_selected_hex());
962 
963  mousehandler.set_current_paths(pathfind::paths());
964 
965  if(un->hold_position()) {
966  mousehandler.cycle_units(false);
967  }
968  }
969 }
970 
971 void menu_handler::end_unit_turn(mouse_handler& mousehandler, int side_num)
972 {
973  const unit_map::iterator un = pc_.get_units().find(mousehandler.get_selected_hex());
974  if(un != pc_.get_units().end() && un->side() == side_num && un->movement_left() >= 0) {
975  un->toggle_user_end_turn();
976  gui_->invalidate(mousehandler.get_selected_hex());
977 
978  mousehandler.set_current_paths(pathfind::paths());
979 
980  if(un->user_end_turn()) {
981  mousehandler.cycle_units(false);
982  }
983 
984  // If cycle_units hasn't found a new unit to cycle to then the original unit is still selected, but
985  // in a state where left-clicking on it does nothing. Make it respond to mouse clicks again.
986  if(un == pc_.get_units().find(mousehandler.get_selected_hex())) {
987  mousehandler.deselect_hex();
988  }
989  }
990 }
991 
992 void menu_handler::search()
993 {
994  std::ostringstream msg;
995  msg << _("Search");
996  if(last_search_hit_.valid()) {
997  msg << " [" << last_search_ << "]";
998  }
999  msg << ':';
1000  textbox_info_.show(gui::TEXTBOX_SEARCH, msg.str(), "", false, *gui_);
1001 }
1002 
1003 void menu_handler::do_speak()
1004 {
1005  // None of the two parameters really needs to be passed since the information belong to members of the class.
1006  // But since it makes the called method more generic, it is done anyway.
1007  chat_handler::do_speak(
1008  textbox_info_.box()->text(), textbox_info_.check() != nullptr ? textbox_info_.check()->checked() : false);
1009 }
1010 
1011 void menu_handler::add_chat_message(const std::time_t& time,
1012  const std::string& speaker,
1013  int side,
1014  const std::string& message,
1016 {
1017  gui_->get_chat_manager().add_chat_message(time, speaker, side, message, type, false);
1018 
1020  config {
1021  "sender", prefs::get().login(),
1022  "message", message,
1024  }
1025  );
1026 }
1027 
1028 // command handler for user :commands. Also understands all chat commands
1029 // via inheritance. This complicates some things a bit.
1030 class console_handler : public map_command_handler<console_handler>, private chat_command_handler
1031 {
1032 public:
1033  // convenience typedef
1036  : chmap()
1038  , menu_handler_(menu_handler)
1039  , team_num_(menu_handler.pc_.current_side())
1040  {
1041  }
1042 
1043  using chmap::dispatch; // disambiguate
1044  using chmap::get_commands_list;
1045  using chmap::command_failed;
1046 
1047 protected:
1048  // chat_command_handler's init_map() and handlers will end up calling these.
1049  // this makes sure the commands end up in our map
1050  virtual void register_command(const std::string& cmd,
1051  chat_command_handler::command_handler h,
1052  const std::string& help = "",
1053  const std::string& usage = "",
1054  const std::string& flags = "")
1055  {
1056  chmap::register_command(cmd, h, help, usage, flags + "N"); // add chat commands as network_only
1057  }
1058 
1059  virtual void register_alias(const std::string& to_cmd, const std::string& cmd)
1060  {
1061  chmap::register_alias(to_cmd, cmd);
1062  }
1063 
1064  virtual std::string get_arg(unsigned i) const
1065  {
1066  return chmap::get_arg(i);
1067  }
1068 
1069  virtual std::string get_cmd() const
1070  {
1071  return chmap::get_cmd();
1072  }
1073 
1074  virtual std::string get_data(unsigned n = 1) const
1075  {
1076  return chmap::get_data(n);
1077  }
1078 
1079  // these are needed to avoid ambiguities introduced by inheriting from console_command_handler
1080  using chmap::register_command;
1081  using chmap::register_alias;
1082  using chmap::help;
1083  using chmap::is_enabled;
1084  using chmap::command_failed_need_arg;
1085 
1086  void do_refresh();
1087  void do_droid();
1088  void do_terrain();
1089  void do_idle();
1090  void do_theme();
1091  void do_control();
1092  void do_controller();
1093  void do_clear();
1094  void do_foreground();
1095  void do_layers();
1096  void do_fps();
1097  void do_benchmark();
1098  void do_save();
1099  void do_save_quit();
1100  void do_quit();
1101  void do_ignore_replay_errors();
1102  void do_nosaves();
1103  void do_next_level();
1104  void do_choose_level();
1105  void do_turn();
1106  void do_turn_limit();
1107  void do_debug();
1108  void do_nodebug();
1109  void do_lua();
1110  void do_unsafe_lua();
1111  void do_custom();
1112  void do_set_alias();
1113  void do_set_var();
1114  void do_show_var();
1115  void do_inspect();
1116  void do_control_dialog();
1117  void do_unit();
1118  // void do_buff();
1119  // void do_unbuff();
1120  void do_discover();
1121  void do_undiscover();
1122  void do_create();
1123  void do_fog();
1124  void do_shroud();
1125  void do_gold();
1126  void do_event();
1127  void do_toggle_draw_coordinates();
1128  void do_toggle_draw_terrain_codes();
1129  void do_toggle_draw_num_of_bitmaps();
1130  void do_toggle_whiteboard();
1131  void do_whiteboard_options();
1132 
1133  std::string get_flags_description() const
1134  {
1135  return _("(D) — debug only, (N) — network only, (A) — admin only");
1136  }
1137 
1138  using chat_command_handler::get_command_flags_description; // silence a warning
1139  std::string get_command_flags_description(const chmap::command& c) const
1140  {
1141  std::string space(" ");
1142  return (c.has_flag('D') ? space + _("(debug command)") : "")
1143  + (c.has_flag('N') ? space + _("(network only)") : "")
1144  + (c.has_flag('A') ? space + _("(admin only)") : "")
1145  + (c.has_flag('S') ? space + _("(not during other events)") : "");
1146  }
1147 
1148  using map::is_enabled;
1149  bool is_enabled(const chmap::command& c) const
1150  {
1151  return !((c.has_flag('D') && !game_config::debug)
1152  || (c.has_flag('N') && !menu_handler_.pc_.is_networked_mp())
1153  || (c.has_flag('A') && !mp::logged_in_as_moderator())
1154  || (c.has_flag('S') && (synced_context::get_synced_state() != synced_context::UNSYNCED || !menu_handler_.pc_.current_team().is_local())));
1155  }
1156 
1157  void print(const std::string& title, const std::string& message)
1158  {
1159  menu_handler_.add_chat_message(std::time(nullptr), title, 0, message);
1160  }
1161 
1162  void init_map()
1163  {
1164  chat_command_handler::init_map(); // grab chat_ /command handlers
1165 
1166  chmap::get_command("log")->flags = ""; // clear network-only flag from log
1167  chmap::get_command("version")->flags = ""; // clear network-only flag
1168  chmap::get_command("ignore")->flags = ""; // clear network-only flag
1169  chmap::get_command("friend")->flags = ""; // clear network-only flag
1170  chmap::get_command("remove")->flags = ""; // clear network-only flag
1171 
1172  chmap::set_cmd_prefix(":");
1173  chmap::set_cmd_flag(true);
1174 
1175  register_command("refresh", &console_handler::do_refresh, _("Refresh gui."));
1176  register_command("droid", &console_handler::do_droid, _("Switch a side to/from AI control."),
1177  // TRANSLATORS: These are the arguments accepted by the "droid" command,
1178  // which must be a side-number and then optionally one of "on", "off" or "full".
1179  // As with the command's name, "on", "off" and "full" are hardcoded, and shouldn't change in the translation.
1180  _("[<side> [on/off/full]]\n“on” = enable but retain vision, “full” = as if it’s controlled by another player"));
1181  register_command("terrain", &console_handler::do_terrain, _("Change terrain type of current hex"),
1182  // TRANSLATORS: [both|base|overlay] are hardcoded literal arguments and shouldn't be translated.
1183  _("<terrain type> [both|base|overlay]"), "DS");
1184  register_command("idle", &console_handler::do_idle, _("Switch a side to/from idle state."),
1185  // TRANSLATORS: These are the arguments accepted by the "idle" command,
1186  // which must be a side-number and then optionally "on" or "off".
1187  // As with the command's name, "on" and "off" are hardcoded, and shouldn't change in the translation.
1188  _("command_idle^[<side> [on/off]]"));
1189  register_command("theme", &console_handler::do_theme, _("Change the in-game theme."));
1190  register_command("control", &console_handler::do_control,
1191  _("Assign control of a side to a different player or observer."), _("<side> <nickname>"), "N");
1192  register_command("controller", &console_handler::do_controller, _("Query the controller status of a side."),
1193  _("<side>"));
1194  register_command("clear", &console_handler::do_clear, _("Clear chat history."));
1195  register_command("foreground", &console_handler::do_foreground, _("Debug foreground terrain."), "", "D");
1196  register_command(
1197  "layers", &console_handler::do_layers, _("Debug layers from terrain under the mouse."), "", "D");
1198  register_command("fps", &console_handler::do_fps, _("Display and log fps (Frames Per Second)."));
1199  register_command("benchmark", &console_handler::do_benchmark, _("Similar to the ‘fps’ command, but also forces everything to redraw instead of only things that have changed."));
1200  register_command("save", &console_handler::do_save, _("Save game."));
1201  register_alias("save", "w");
1202  register_command("quit", &console_handler::do_quit, _("Quit game."));
1203  // Note the next value is used hardcoded in the init tests.
1204  register_alias("quit", "q!");
1205  register_command("save_quit", &console_handler::do_save_quit, _("Save and quit."));
1206  register_alias("save_quit", "wq");
1207  register_command("ignore_replay_errors", &console_handler::do_ignore_replay_errors, _("Ignore replay errors."));
1208  register_command("nosaves", &console_handler::do_nosaves, _("Disable autosaves."));
1209  register_command("next_level", &console_handler::do_next_level,
1210  _("Advance to the next scenario, or scenario identified by ‘id’"), _("<id>"), "DS");
1211  register_alias("next_level", "n");
1212  register_command("choose_level", &console_handler::do_choose_level, _("Choose next scenario"), "", "DS");
1213  register_alias("choose_level", "cl");
1214  register_command("turn", &console_handler::do_turn,
1215  _("Change turn number (and time of day), or increase by one if no number is specified."), _("[turn]"),
1216  "DS");
1217  register_command("turn_limit", &console_handler::do_turn_limit,
1218  _("Change turn limit, or turn the turn limit off if no number is specified or it’s −1."), _("[limit]"),
1219  "DS");
1220  register_command("debug", &console_handler::do_debug, _("Turn debug mode on."));
1221  register_command("nodebug", &console_handler::do_nodebug, _("Turn debug mode off."), "", "D");
1222  register_command(
1223  "lua", &console_handler::do_lua, _("Execute a Lua statement."), _("<command>[;<command>...]"), "DS");
1224  register_command(
1225  "unsafe_lua", &console_handler::do_unsafe_lua, _("Grant higher privileges to Lua scripts."), "", "D");
1226  register_command("custom", &console_handler::do_custom, _("Set the command used by the custom command hotkey"),
1227  _("<command>[;<command>...]"));
1228  register_command("give_control", &console_handler::do_control_dialog,
1229  _("Invoke a dialog allowing changing control of MP sides."), "", "N");
1230  register_command("inspect", &console_handler::do_inspect, _("Launch the gamestate inspector"), "", "D");
1231  register_command(
1232  "alias", &console_handler::do_set_alias, _("Set or show alias to a command"), _("<name>[=<command>]"));
1233  register_command(
1234  "set_var", &console_handler::do_set_var, _("Set a scenario variable."), _("<var>=<value>"), "DS");
1235  register_command("show_var", &console_handler::do_show_var, _("Show a scenario variable."), _("<var>"), "D");
1236  register_command("unit", &console_handler::do_unit,
1237  // TRANSLATORS: Do not translate the word "advances"; it is a hardcoded literal argument.
1238  _("Modify a unit variable. (Only top level keys are supported, and advances=<number>.)"),
1239  _("<var>=<value>"), "DS");
1240 
1241  // register_command("buff", &console_handler::do_buff,
1242  // _("Add a trait to a unit."), "", "D");
1243  // register_command("unbuff", &console_handler::do_unbuff,
1244  // _("Remove a trait from a unit. (Does not work yet.)"), "", "D");
1245  register_command("discover", &console_handler::do_discover, _("Discover all units in help."), "");
1246  register_command("undiscover", &console_handler::do_undiscover, _("‘Undiscover’ all units in help."), "");
1247  register_command("create", &console_handler::do_create, _("Create a unit."), _("<unit type id>"), "DS");
1248  register_command("fog", &console_handler::do_fog, _("Toggle fog for the current player."), "", "DS");
1249  register_command("shroud", &console_handler::do_shroud, _("Toggle shroud for the current player."), "", "DS");
1250  register_command("gold", &console_handler::do_gold, _("Give gold to the current player."), _("<amount>"), "DS");
1251  register_command("throw", &console_handler::do_event, _("Fire a game event."), _("<event name>"), "DS");
1252  register_alias("throw", "fire");
1253  register_command("show_coordinates", &console_handler::do_toggle_draw_coordinates,
1254  _("Toggle overlaying of x,y coordinates on hexes."));
1255  register_alias("show_coordinates", "sc");
1256  register_command("show_terrain_codes", &console_handler::do_toggle_draw_terrain_codes,
1257  _("Toggle overlaying of terrain codes on hexes."));
1258  register_alias("show_terrain_codes", "tc");
1259  register_command("show_num_of_bitmaps", &console_handler::do_toggle_draw_num_of_bitmaps,
1260  _("Toggle overlaying of number of bitmaps on hexes."));
1261  register_alias("show_num_of_bitmaps", "bn");
1262  register_command("whiteboard", &console_handler::do_toggle_whiteboard, _("Toggle planning mode."));
1263  register_alias("whiteboard", "wb");
1264  register_command(
1265  "whiteboard_options", &console_handler::do_whiteboard_options, _("Access whiteboard options dialog."));
1266  register_alias("whiteboard_options", "wbo");
1267 
1268  if(auto alias_list = prefs::get().get_alias()) {
1269  for(const auto& [key, value] : alias_list->attribute_range()) {
1270  register_alias(value, key);
1271  }
1272  }
1273  }
1274 
1275 private:
1277  const unsigned int team_num_;
1278 };
1279 
1280 void menu_handler::send_chat_message(const std::string& message, bool allies_only)
1281 {
1282  config cfg;
1283  cfg["id"] = prefs::get().login();
1284  cfg["message"] = message;
1285  auto now = std::chrono::system_clock::now();
1286  cfg["time"] = chrono::serialize_timestamp(now);
1287 
1288  const int side = board().is_observer() ? 0 : gui_->viewing_team().side();
1289  if(!board().is_observer()) {
1290  cfg["side"] = side;
1291  }
1292 
1293  bool private_message = has_friends() && allies_only;
1294 
1295  if(private_message) {
1296  if(board().is_observer()) {
1297  cfg["to_sides"] = game_config::observer_team_name;
1298  } else {
1299  cfg["to_sides"] = gui_->viewing_team().allied_human_teams();
1300  }
1301  }
1302 
1303  resources::recorder->speak(cfg);
1304 
1305  auto as_time_t = std::chrono::system_clock::to_time_t(now); // FIXME: remove
1306  add_chat_message(as_time_t, cfg["id"], side, message,
1308 }
1309 
1310 void menu_handler::do_search(const std::string& new_search)
1311 {
1312  if(new_search.empty() == false && new_search != last_search_)
1313  last_search_ = new_search;
1314 
1315  if(last_search_.empty())
1316  return;
1317 
1318  bool found = false;
1319  map_location loc = last_search_hit_;
1320  // If this is a location search, just center on that location.
1321  std::vector<std::string> args = utils::split(last_search_, ',');
1322  if(args.size() == 2) {
1323  int x, y;
1324  x = lexical_cast_default<int>(args[0], 0) - 1;
1325  y = lexical_cast_default<int>(args[1], 0) - 1;
1326  if(x >= 0 && x < pc_.get_map().w() && y >= 0 && y < pc_.get_map().h()) {
1327  loc = map_location(x, y);
1328  found = true;
1329  }
1330  }
1331  // Start scanning the game map
1332  if(loc.valid() == false) {
1333  loc = map_location(pc_.get_map().w() - 1, pc_.get_map().h() - 1);
1334  }
1335 
1337  while(!found) {
1338  // Move to the next location
1339  loc.x = (loc.x + 1) % pc_.get_map().w();
1340  if(loc.x == 0)
1341  loc.y = (loc.y + 1) % pc_.get_map().h();
1342 
1343  // Search label
1344  if(!gui_->shrouded(loc)) {
1345  const terrain_label* label = gui_->labels().get_label(loc);
1346  if(label) {
1347  const std::string& label_text = label->text().str();
1348  found = translation::ci_search(label_text, last_search_);
1349  }
1350  }
1351 
1352  // Search unit name
1353  if(!gui_->fogged(loc)) {
1354  unit_map::const_iterator ui = pc_.get_units().find(loc);
1355  if(ui != pc_.get_units().end()) {
1356  const std::string& unit_name = ui->name();
1357  if(translation::ci_search(unit_name, last_search_)) {
1358  if(!gui_->viewing_team().is_enemy(ui->side())
1359  || !ui->invisible(ui->get_location())) {
1360  found = true;
1361  }
1362  }
1363  }
1364  }
1365 
1366  if(loc == start)
1367  break;
1368  }
1369 
1370  if(found) {
1371  last_search_hit_ = loc;
1372  gui_->scroll_to_tile(loc, game_display::ONSCREEN, false);
1373  gui_->highlight_hex(loc);
1374  } else {
1375  last_search_hit_ = map_location();
1376  // Not found, inform the player
1377  utils::string_map symbols;
1378  symbols["search"] = last_search_;
1379  const std::string msg = VGETTEXT("Could not find label or unit "
1380  "containing the string ‘$search’.",
1381  symbols);
1382  (void) gui2::show_message("", msg, message::auto_close);
1383  }
1384 }
1385 
1386 void menu_handler::do_command(const std::string& str)
1387 {
1388  console_handler ch(*this);
1389  ch.dispatch(str);
1390 }
1391 
1392 std::vector<std::string> menu_handler::get_commands_list()
1393 {
1394  console_handler ch(*this);
1395  // HACK: we need to call dispatch() at least once to get the
1396  // command list populated *at all*. Terrible design.
1397  // An empty command is silently ignored and has negligible
1398  // overhead, so we use that for this purpose here.
1399  ch.dispatch("");
1400  return ch.get_commands_list();
1401 }
1402 
1403 void console_handler::do_refresh()
1404 {
1407 
1408  menu_handler_.gui_->create_buttons();
1409  menu_handler_.gui_->queue_rerender();
1410 }
1411 
1412 void console_handler::do_droid()
1413 {
1414  // :droid [<side> [on/off/full]]
1415  const std::string side_s = get_arg(1);
1416  std::string action = get_arg(2);
1417  std::transform(action.begin(), action.end(), action.begin(), tolower);
1418  // default to the current side if empty
1419  const unsigned int side = side_s.empty() ? team_num_ : lexical_cast_default<unsigned int>(side_s);
1420  const bool is_your_turn = menu_handler_.pc_.current_side() == static_cast<int>(menu_handler_.gui_->viewing_team().side());
1421 
1422  utils::string_map symbols;
1423  symbols["side"] = std::to_string(side);
1424 
1425  if(side < 1 || side > menu_handler_.pc_.get_teams().size()) {
1426  command_failed(VGETTEXT("Can’t droid invalid side: ‘$side’.", symbols));
1427  return;
1428  } else if(menu_handler_.board().get_team(side).is_network()) {
1429  command_failed(VGETTEXT("Can’t droid networked side: ‘$side’.", symbols));
1430  return;
1431  } else if(menu_handler_.board().get_team(side).is_local()) {
1432  bool changed = false;
1433 
1434  const bool is_human = menu_handler_.board().get_team(side).is_human();
1435  const bool is_droid = menu_handler_.board().get_team(side).is_droid();
1436  const bool is_proxy_human = menu_handler_.board().get_team(side).is_proxy_human();
1437  const bool is_ai = menu_handler_.board().get_team(side).is_ai();
1438 
1439  if(action == "on") {
1440  if(is_ai && !is_your_turn) {
1441  command_failed(_("It is not allowed to change a side from AI to human control when it’s not your turn."));
1442  return;
1443  }
1444  if(!is_human || !is_droid) {
1445  menu_handler_.board().get_team(side).make_human();
1446  menu_handler_.board().get_team(side).make_droid();
1447  changed = true;
1448  if(is_ai) {
1449  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1450  }
1451  print(get_cmd(), VGETTEXT("Side ‘$side’ controller is now controlled by: AI.", symbols));
1452  } else {
1453  print(get_cmd(), VGETTEXT("Side ‘$side’ is already droided.", symbols));
1454  }
1455  } else if(action == "off") {
1456  if(is_ai && !is_your_turn) {
1457  command_failed(_("It is not allowed to change a side from AI to human control when it’s not your turn."));
1458  return;
1459  }
1460  if(!is_human || !is_proxy_human) {
1461  menu_handler_.board().get_team(side).make_human();
1462  menu_handler_.board().get_team(side).make_proxy_human();
1463  changed = true;
1464  if(is_ai) {
1465  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1466  }
1467  print(get_cmd(), VGETTEXT("Side ‘$side’ controller is now controlled by: human.", symbols));
1468  } else {
1469  print(get_cmd(), VGETTEXT("Side ‘$side’ is already not droided.", symbols));
1470  }
1471  } else if(action == "full") {
1472  if(!is_your_turn) {
1473  command_failed(_("It is not allowed to change a side from human to AI control when it’s not your turn."));
1474  return;
1475  }
1476  if(!is_ai || !is_droid) {
1477  menu_handler_.board().get_team(side).make_ai();
1478  menu_handler_.board().get_team(side).make_droid();
1479  changed = true;
1480  if(is_human || is_proxy_human) {
1481  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::ai}});
1482  }
1483  print(get_cmd(), VGETTEXT("Side ‘$side’ controller is now fully controlled by: AI.", symbols));
1484  } else {
1485  print(get_cmd(), VGETTEXT("Side ‘$side’ is already fully AI controlled.", symbols));
1486  }
1487  } else if(action == "") {
1488  if(is_ai && !is_your_turn) {
1489  command_failed(_("It is not allowed to change a side from AI to human control when it’s not your turn."));
1490  return;
1491  }
1492  if(is_ai || is_droid) {
1493  menu_handler_.board().get_team(side).make_human();
1494  menu_handler_.board().get_team(side).make_proxy_human();
1495  changed = true;
1496  if(is_ai) {
1497  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1498  }
1499  print(get_cmd(), VGETTEXT("Side ‘$side’ controller is now controlled by: human.", symbols));
1500  } else {
1501  menu_handler_.board().get_team(side).make_human();
1502  menu_handler_.board().get_team(side).make_droid();
1503  changed = true;
1504  if(is_ai) {
1505  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1506  }
1507  print(get_cmd(), VGETTEXT("Side ‘$side’ controller is now controlled by: AI.", symbols));
1508  }
1509  } else {
1510  print(get_cmd(), VGETTEXT("Invalid action provided for side ‘$side’. Valid actions are: on, off, full.", symbols));
1511  }
1512 
1513  if(team_num_ == side && changed) {
1514  if(playsingle_controller* psc = dynamic_cast<playsingle_controller*>(&menu_handler_.pc_)) {
1515  psc->set_player_type_changed();
1516  }
1517  }
1518  } else {
1519  command_failed(VGETTEXT("Side ‘$side’ is not a human or AI player.", symbols));
1520  return;
1521  }
1522  menu_handler_.textbox_info_.close();
1523 }
1524 
1525 void console_handler::do_terrain()
1526 {
1527  // :terrain [<terrain type> [both|base|overlay]]
1528  const std::string terrain_type = get_arg(1);
1529  const std::string mode_str = get_arg(2);
1530 
1531  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
1532  const map_location& loc = mousehandler.get_last_hex();
1533 
1534  synced_context::run_and_throw("debug_terrain",
1535  config {
1536  "x", loc.wml_x(),
1537  "y", loc.wml_y(),
1538  "terrain_type", terrain_type,
1539  "mode_str", mode_str,
1540  }
1541  );
1542 }
1543 
1544 void console_handler::do_idle()
1545 {
1546  // :idle [<side> [on/off]]
1547  const std::string side_s = get_arg(1);
1548  const std::string action = get_arg(2);
1549  // default to the current side if empty
1550  const unsigned int side = side_s.empty() ? team_num_ : lexical_cast_default<unsigned int>(side_s);
1551 
1552  if(side < 1 || side > menu_handler_.pc_.get_teams().size()) {
1553  utils::string_map symbols;
1554  symbols["side"] = side_s;
1555  command_failed(VGETTEXT("Can’t idle invalid side: ‘$side’.", symbols));
1556  return;
1557  } else if(menu_handler_.board().get_team(side).is_network()) {
1558  utils::string_map symbols;
1559  symbols["side"] = std::to_string(side);
1560  command_failed(VGETTEXT("Can’t idle networked side: ‘$side’.", symbols));
1561  return;
1562  } else if(menu_handler_.board().get_team(side).is_local_ai()) {
1563  utils::string_map symbols;
1564  symbols["side"] = std::to_string(side);
1565  command_failed(VGETTEXT("Can’t idle local ai side: ‘$side’.", symbols));
1566  return;
1567  } else if(menu_handler_.board().get_team(side).is_local_human()) {
1568  if(menu_handler_.board().get_team(side).is_idle() ? action == " on" : action == " off") {
1569  return;
1570  }
1571  // toggle the proxy controller between idle / non idle
1572  menu_handler_.board().get_team(side).toggle_idle();
1573  if(team_num_ == side) {
1574  if(playsingle_controller* psc = dynamic_cast<playsingle_controller*>(&menu_handler_.pc_)) {
1575  psc->set_player_type_changed();
1576  }
1577  }
1578  }
1579  menu_handler_.textbox_info_.close();
1580 }
1581 
1582 void console_handler::do_theme()
1583 {
1585 }
1586 
1588 {
1589  save_id_matches(const std::string& save_id)
1590  : save_id_(save_id)
1591  {
1592  }
1593 
1594  bool operator()(const team& t) const
1595  {
1596  return t.save_id() == save_id_;
1597  }
1598 
1599  std::string save_id_;
1600 };
1601 
1602 void console_handler::do_control()
1603 {
1604  // :control <side> <nick>
1605  if(!menu_handler_.pc_.is_networked_mp()) {
1606  return;
1607  }
1608 
1609  const std::string side = get_arg(1);
1610  const std::string player = get_arg(2);
1611  if(player.empty()) {
1612  command_failed_need_arg(2);
1613  return;
1614  }
1615 
1616  unsigned int side_num;
1617  try {
1618  side_num = lexical_cast<unsigned int>(side);
1619  } catch(const bad_lexical_cast&) {
1620  const auto& teams = menu_handler_.pc_.get_teams();
1621  const auto it_t = std::find_if(teams.begin(), teams.end(), save_id_matches(side));
1622 
1623  if(it_t == teams.end()) {
1624  utils::string_map symbols;
1625  symbols["side"] = side;
1626  command_failed(VGETTEXT("Can’t change control of invalid side: ‘$side’.", symbols));
1627  return;
1628  } else {
1629  side_num = it_t->side();
1630  }
1631  }
1632 
1633  if(side_num < 1 || side_num > menu_handler_.pc_.get_teams().size()) {
1634  utils::string_map symbols;
1635  symbols["side"] = side;
1636  command_failed(VGETTEXT("Can’t change control of out-of-bounds side: ‘$side’.", symbols));
1637  return;
1638  }
1639 
1640  menu_handler_.request_control_change(side_num, player);
1641  menu_handler_.textbox_info_.close();
1642 }
1643 
1644 void console_handler::do_controller()
1645 {
1646  const std::string side = get_arg(1);
1647  unsigned int side_num;
1648  try {
1649  side_num = lexical_cast<unsigned int>(side);
1650  } catch(const bad_lexical_cast&) {
1651  utils::string_map symbols;
1652  symbols["side"] = side;
1653  command_failed(VGETTEXT("Can’t query control of invalid side: ‘$side’.", symbols));
1654  return;
1655  }
1656 
1657  if(side_num < 1 || side_num > menu_handler_.pc_.get_teams().size()) {
1658  utils::string_map symbols;
1659  symbols["side"] = side;
1660  command_failed(VGETTEXT("Can’t query control of out-of-bounds side: ‘$side’.", symbols));
1661  return;
1662  }
1663 
1664  std::string report = side_controller::get_string(menu_handler_.board().get_team(side_num).controller());
1665  if(!menu_handler_.board().get_team(side_num).is_proxy_human()) {
1666  report += " (" + side_proxy_controller::get_string(menu_handler_.board().get_team(side_num).proxy_controller()) + ")";
1667  }
1668 
1669  if(menu_handler_.board().get_team(side_num).is_network()) {
1670  report += " (networked)";
1671  }
1672 
1673  print(get_cmd(), report);
1674 }
1675 
1676 void console_handler::do_clear()
1677 {
1678  menu_handler_.gui_->get_chat_manager().clear_chat_messages();
1679 }
1680 
1681 void console_handler::do_foreground()
1682 {
1683  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_FOREGROUND);
1684  menu_handler_.gui_->invalidate_all();
1685 }
1686 
1687 void console_handler::do_layers()
1688 {
1689  display& disp = *(menu_handler_.gui_);
1690 
1691  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
1692  const map_location& loc = mousehandler.get_last_hex();
1693 
1694  //
1695  // It's possible to invoke this dialog even if loc isn't a valid hex. I'm not sure
1696  // exactly how that happens, but it does seem to occur when moving the mouse outside
1697  // the window to the menu bar. Not sure if there's a bug when the set-last-hex code
1698  // in that case, but this check at least ensures the dialog is only ever shown for a
1699  // valid, on-map location. Otherwise, an assertion gets thrown.
1700  //
1701  // -- vultraz, 2017-09-21
1702  //
1703  if(menu_handler_.pc_.get_map().on_board_with_border(loc)) {
1704  terrain_layers::display(disp, loc);
1705  }
1706 }
1707 
1708 void console_handler::do_fps()
1709 {
1710  prefs::get().set_show_fps(!prefs::get().show_fps());
1711 }
1712 
1713 void console_handler::do_benchmark()
1714 {
1715  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_BENCHMARK);
1716 }
1717 
1718 void console_handler::do_save()
1719 {
1720  menu_handler_.pc_.do_consolesave(get_data());
1721 }
1722 
1723 void console_handler::do_save_quit()
1724 {
1725  do_save();
1726  do_quit();
1727 }
1728 
1729 void console_handler::do_quit()
1730 {
1732 }
1733 
1734 void console_handler::do_ignore_replay_errors()
1735 {
1736  game_config::ignore_replay_errors = (get_data() != "off") ? true : false;
1737 }
1738 
1739 void console_handler::do_nosaves()
1740 {
1741  game_config::disable_autosave = (get_data() != "off") ? true : false;
1742 }
1743 
1744 void console_handler::do_next_level()
1745 {
1746  synced_context::run_and_throw("debug_next_level", config {"next_level", get_data()});
1747 }
1748 
1749 void console_handler::do_choose_level()
1750 {
1751  std::string tag = menu_handler_.pc_.get_classification().get_tagname();
1752  std::vector<std::string> options;
1753  std::string next;
1754  if(tag != "multiplayer") {
1755  for(const config& sc : menu_handler_.game_config_.child_range(tag)) {
1756  const std::string& id = sc["id"];
1757  options.push_back(id);
1758  if(id == menu_handler_.gamedata().next_scenario()) {
1759  next = id;
1760  }
1761  }
1762  } else {
1763  // find scenarios of multiplayer campaigns
1764  // (assumes that scenarios are ordered properly in the game_config)
1765  std::string scenario_id = menu_handler_.pc_.get_mp_settings().mp_scenario;
1766  if(auto this_scenario = menu_handler_.game_config_.find_child(tag, "id", scenario_id)) {
1767  std::string addon_id = this_scenario["addon_id"].str();
1768  for(const config& sc : menu_handler_.game_config_.child_range(tag)) {
1769  if(sc["addon_id"] == addon_id) {
1770  std::string id = sc["id"];
1771  options.push_back(id);
1772  if(id == menu_handler_.gamedata().next_scenario()) {
1773  next = id;
1774  }
1775  }
1776  }
1777  }
1778  }
1779  std::sort(options.begin(), options.end());
1780  int choice = std::distance(options.begin(), std::lower_bound(options.begin(), options.end(), next));
1781  {
1782  simple_item_selector dlg(_("Choose Scenario (Debug!)"), "", options);
1783  dlg.set_selected_index(choice);
1784  dlg.show();
1785  choice = dlg.selected_index();
1786  }
1787 
1788  if(choice == -1) {
1789  return;
1790  }
1791 
1792  if(std::size_t(choice) < options.size()) {
1793  synced_context::run_and_throw("debug_next_level", config {"next_level", options[choice]});
1794  }
1795 }
1796 
1797 void console_handler::do_turn()
1798 {
1799  tod_manager& tod_man = menu_handler_.gamestate().tod_manager_;
1800 
1801  int turn = tod_man.turn() + 1;
1802  const std::string& data = get_data();
1803  if(!data.empty()) {
1804  turn = lexical_cast_default<int>(data, 1);
1805  }
1806  synced_context::run_and_throw("debug_turn", config {"turn", turn});
1807 }
1808 
1809 void console_handler::do_turn_limit()
1810 {
1811  int limit = get_data().empty() ? -1 : lexical_cast_default<int>(get_data(), 1);
1812  synced_context::run_and_throw("debug_turn_limit", config {"turn_limit", limit});
1813 }
1814 
1815 void console_handler::do_debug()
1816 {
1817  if(!menu_handler_.pc_.is_networked_mp() || game_config::mp_debug) {
1818  print(get_cmd(), _("Debug mode activated!"));
1819  game_config::set_debug(true);
1820  } else {
1821  command_failed(_("Debug mode not available in network games"));
1822  }
1823 }
1824 
1825 void console_handler::do_nodebug()
1826 {
1827  if(game_config::debug) {
1828  print(get_cmd(), _("Debug mode deactivated!"));
1829  game_config::set_debug(false);
1830  }
1831 }
1832 
1833 void console_handler::do_lua()
1834 {
1835  if(!menu_handler_.gamestate().lua_kernel_) {
1836  return;
1837  }
1838 
1839  synced_context::run_and_throw("debug_lua", config {"code", get_data()});
1840 }
1841 
1842 void console_handler::do_unsafe_lua()
1843 {
1844  if(!menu_handler_.gamestate().lua_kernel_) {
1845  return;
1846  }
1847 
1848  const int retval = gui2::show_message(_("WARNING! Unsafe Lua Mode"),
1849  _("Executing Lua code in in this manner opens your computer to potential security breaches from any "
1850  "malicious add-ons or other programs you may have installed.\n\n"
1851  "Do not continue unless you really know what you are doing."), message::ok_cancel_buttons);
1852 
1853  if(retval == gui2::retval::OK) {
1854  print(get_cmd(), _("Unsafe mode enabled!"));
1855  menu_handler_.gamestate().lua_kernel_->load_package();
1856  }
1857 }
1858 
1859 void console_handler::do_custom()
1860 {
1861  prefs::get().set_custom_command(get_data());
1862 }
1863 
1864 void console_handler::do_set_alias()
1865 {
1866  const std::string data = get_data();
1867  const std::string::const_iterator j = std::find(data.begin(), data.end(), '=');
1868  const std::string alias(data.begin(), j);
1869  if(j != data.end()) {
1870  const std::string command(j + 1, data.end());
1871  if(!command.empty()) {
1872  register_alias(command, alias);
1873  } else {
1874  // "alias something=" deactivate this alias. We just set it
1875  // equal to itself here. Later preferences will filter empty alias.
1876  register_alias(alias, alias);
1877  }
1878  prefs::get().add_alias(alias, command);
1879  // directly save it for the moment, but will slow commands sequence
1881  } else {
1882  // "alias something" display its value
1883  // if no alias, will be "'something' = 'something'"
1884  const std::string command = chmap::get_actual_cmd(alias);
1885  print(get_cmd(), "'" + alias + "'" + " = " + "'" + command + "'");
1886  }
1887 }
1888 
1889 void console_handler::do_set_var()
1890 {
1891  const std::string data = get_data();
1892  if(data.empty()) {
1893  command_failed_need_arg(1);
1894  return;
1895  }
1896 
1897  const std::string::const_iterator j = std::find(data.begin(), data.end(), '=');
1898  if(j != data.end()) {
1899  const std::string name(data.begin(), j);
1900  const std::string value(j + 1, data.end());
1901  synced_context::run_and_throw("debug_set_var", config {"name", name, "value", value});
1902  } else {
1903  command_failed(_("Variable not found"));
1904  }
1905 }
1906 
1907 void console_handler::do_show_var()
1908 {
1909  gui2::show_transient_message("", menu_handler_.gamedata().get_variable_const(get_data()));
1910 }
1911 
1912 void console_handler::do_inspect()
1913 {
1915  gamestate_inspector::display(
1916  menu_handler_.gamedata().get_variables(), *resources::game_events, menu_handler_.board());
1917 }
1918 
1919 void console_handler::do_control_dialog()
1920 {
1921  mp_change_control::display(menu_handler_);
1922 }
1923 
1924 void console_handler::do_unit()
1925 {
1926  // prevent SIGSEGV due to attempt to set HP during a fight
1927  if(events::commands_disabled > 0) {
1928  return;
1929  }
1930 
1931  unit_map::iterator i = menu_handler_.current_unit();
1932  if(i == menu_handler_.pc_.get_units().end()) {
1933  utils::string_map symbols;
1934  symbols["unit"] = get_arg(1);
1935  command_failed(VGETTEXT(
1936  "Debug command ‘unit: $unit’ failed: no unit selected or hovered over.",
1937  symbols));
1938  return;
1939  }
1940 
1941  const map_location loc = i->get_location();
1942  const std::string data = get_data(1);
1943  std::vector<std::string> parameters = utils::split(data, '=', utils::STRIP_SPACES);
1944  if(parameters.size() < 2) {
1945  return;
1946  }
1947 
1948  if(parameters[0] == "alignment") {
1949  auto alignment = unit_alignments::get_enum(parameters[1]);
1950  if(!alignment) {
1951  utils::string_map symbols;
1952  symbols["alignment"] = get_arg(1);
1953  command_failed(VGETTEXT(
1954  "Invalid alignment: ‘$alignment’, needs to be one of lawful, neutral, chaotic, or liminal.",
1955  symbols));
1956  return;
1957  }
1958  }
1959 
1960  synced_context::run_and_throw("debug_unit",
1961  config {
1962  "x", loc.wml_x(),
1963  "y", loc.wml_y(),
1964  "name", parameters[0],
1965  "value", parameters[1],
1966  }
1967  );
1968 }
1969 
1970 void console_handler::do_discover()
1971 {
1972  for(const unit_type_data::unit_type_map::value_type& i : unit_types.types()) {
1973  prefs::get().encountered_units().insert(i.second.id());
1974  }
1975 }
1976 
1977 void console_handler::do_undiscover()
1978 {
1979  const int res = gui2::show_message("Undiscover",
1980  _("Do you wish to clear all of your discovered units from help?"), message::yes_no_buttons);
1981  if(res != gui2::retval::CANCEL) {
1982  prefs::get().encountered_units().clear();
1983  }
1984 }
1985 
1986 /** Implements the (debug mode) console command that creates a unit. */
1987 void console_handler::do_create()
1988 {
1989  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
1990  const map_location& loc = mousehandler.get_last_hex();
1991  if(menu_handler_.pc_.get_map().on_board(loc)) {
1992  const unit_type* ut = unit_types.find(get_data());
1993  if(!ut) {
1994  command_failed(_("Invalid unit type"));
1995  return;
1996  }
1997 
1998  // Create the unit.
1999  create_and_place(*menu_handler_.gui_, menu_handler_.pc_.get_map(), menu_handler_.pc_.get_units(), loc, *ut);
2000  } else {
2001  command_failed(_("Invalid location"));
2002  }
2003 }
2004 
2005 void console_handler::do_fog()
2006 {
2007  synced_context::run_and_throw("debug_fog", config());
2008 }
2009 
2010 void console_handler::do_shroud()
2011 {
2012  synced_context::run_and_throw("debug_shroud", config());
2013 }
2014 
2015 void console_handler::do_gold()
2016 {
2017  synced_context::run_and_throw("debug_gold", config {"gold", lexical_cast_default<int>(get_data(), 1000)});
2018 }
2019 
2020 void console_handler::do_event()
2021 {
2022  synced_context::run_and_throw("debug_event", config {"eventname", get_data()});
2023 }
2024 
2025 void console_handler::do_toggle_draw_coordinates()
2026 {
2027  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_COORDINATES);
2028  menu_handler_.gui_->invalidate_all();
2029 }
2030 void console_handler::do_toggle_draw_terrain_codes()
2031 {
2032  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_TERRAIN_CODES);
2033  menu_handler_.gui_->invalidate_all();
2034 }
2035 
2036 void console_handler::do_toggle_draw_num_of_bitmaps()
2037 {
2038  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_NUM_BITMAPS);
2039  menu_handler_.gui_->invalidate_all();
2040 }
2041 
2042 void console_handler::do_toggle_whiteboard()
2043 {
2044  if(const std::shared_ptr<wb::manager>& whiteb = menu_handler_.pc_.get_whiteboard()) {
2045  whiteb->set_active(!whiteb->is_active());
2046  if(whiteb->is_active()) {
2047  print(get_cmd(), _("Planning mode activated!"));
2048  whiteb->print_help_once();
2049  } else {
2050  print(get_cmd(), _("Planning mode deactivated!"));
2051  }
2052  }
2053 }
2054 
2055 void console_handler::do_whiteboard_options()
2056 {
2057  if(menu_handler_.pc_.get_whiteboard()) {
2058  menu_handler_.pc_.get_whiteboard()->options_dlg();
2059  }
2060 }
2061 
2062 void menu_handler::do_ai_formula(const std::string& str, int side_num, mouse_handler& /*mousehandler*/)
2063 {
2064  try {
2065  add_chat_message(std::time(nullptr), "wfl", 0, ai::manager::get_singleton().evaluate_command(side_num, str));
2066  } catch(const wfl::formula_error&) {
2067  } catch(...) {
2068  add_chat_message(std::time(nullptr), "wfl", 0, "UNKNOWN ERROR IN FORMULA: "+utils::get_unknown_exception_type());
2069  }
2070 }
2071 
2072 void menu_handler::user_command()
2073 {
2074  textbox_info_.show(gui::TEXTBOX_COMMAND, translation::sgettext("prompt^Command:"), "", false, *gui_);
2075 }
2076 
2077 void menu_handler::request_control_change(int side_num, const std::string& player)
2078 {
2079  std::string side = std::to_string(side_num);
2080  if(board().get_team(side_num).is_local_human() && player == prefs::get().login()) {
2081  // this is already our side.
2082  return;
2083  } else {
2084  // The server will (or won't because we aren't allowed to change the controller)
2085  // send us a [change_controller] back, which we then handle in playturn.cpp
2086  pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", player}});
2087  }
2088 }
2089 
2090 void menu_handler::custom_command()
2091 {
2092  for(const std::string& command : utils::split(prefs::get().custom_command(), ';')) {
2093  do_command(command);
2094  }
2095 }
2096 
2097 void menu_handler::ai_formula()
2098 {
2099  if(!pc_.is_networked_mp()) {
2100  textbox_info_.show(gui::TEXTBOX_AI, translation::sgettext("prompt^Formula:"), "", false, *gui_);
2101  }
2102 }
2103 
2104 void menu_handler::clear_messages()
2105 {
2106  gui_->get_chat_manager().clear_chat_messages(); // also clear debug-messages and WML-error-messages
2107 }
2108 
2110 {
2111  pc_.send_to_wesnothd(cfg);
2112 }
2113 
2114 } // end namespace events
map_location loc
Definition: move.cpp:172
Various functions related to moving units.
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
double t
Definition: astarsearch.cpp:63
static manager & get_singleton()
Definition: manager.hpp:140
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
bool is_observer() const
Check if we are an observer in this game.
can_move_result unit_can_move(const unit &u) const
Work out what u can do - this does not check which player's turn is currently active,...
virtual const unit_map & units() const =0
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:97
const team & viewing_team() const
Definition: display.cpp:344
@ ONSCREEN
Definition: display.hpp:509
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition: display.hpp:921
@ DEBUG_BENCHMARK
Toggle to continuously redraw the whole map.
Definition: display.hpp:933
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition: display.hpp:927
@ DEBUG_FOREGROUND
Separates background and foreground terrain layers.
Definition: display.hpp:930
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition: display.hpp:924
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:1985
const display_context & context() const
Definition: display.hpp:193
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2269
virtual std::string get_arg(unsigned i) const
std::string get_flags_description() const
virtual std::string get_cmd() const
console_handler(menu_handler &menu_handler)
void print(const std::string &title, const std::string &message)
virtual void register_alias(const std::string &to_cmd, const std::string &cmd)
bool is_enabled(const chmap::command &c) const
virtual std::string get_data(unsigned n=1) const
menu_handler & menu_handler_
std::string get_command_flags_description(const chmap::command &c) const
virtual void register_command(const std::string &cmd, chat_command_handler::command_handler h, const std::string &help="", const std::string &usage="", const std::string &flags="")
map_command_handler< console_handler > chmap
const unsigned int team_num_
std::vector< std::string > get_commands_list() const
game_board & board() const
gui::floating_textbox & get_textbox()
game_data & gamedata()
gui::floating_textbox textbox_info_
void show_statistics(int side_num)
void recruit(int side_num, const map_location &last_hex)
play_controller & pc_
game_display * gui_
bool has_friends() const
game_state & gamestate() const
void disable_units_highlight()
Use this to disable hovering an unit from highlighting its movement range.
void set_current_paths(const pathfind::paths &new_paths)
pathfind::marked_route get_route(const unit *un, map_location go_to, const team &team) const
play_controller & pc_
map_location get_selected_hex() const
const map_location hovered_hex() const
Uses SDL and game_display::hex_clicked_on to fetch the hex the mouse is hovering, if applicable.
const map_location & get_last_hex() const
bool hex_hosts_unit(const map_location &hex) const
Unit exists on the hex, no matter if friend or foe.
void cycle_units(const bool browse, const bool reverse=false)
Game board class.
Definition: game_board.hpp:47
team & get_team(int i)
Definition: game_board.hpp:92
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
Definition: game_board.cpp:185
void scroll_to_leader(int side, SCROLL_TYPE scroll_type=ONSCREEN, bool force=true)
Scrolls to the leader of a certain side.
virtual const std::set< std::string > & observers() const override
virtual void select_hex(map_location hex) override
Function to display a location as selected.
game_board board_
Definition: game_state.hpp:44
game_data gamedata_
Definition: game_state.hpp:43
Encapsulates the map of the game.
Definition: map.hpp:172
std::string write() const
Definition: map.cpp:210
file_dialog & set_extension(const std::string &value)
Sets allowed file extensions for file names in save mode.
file_dialog & set_path(const std::string &value)
Sets the initial file selection.
file_dialog & set_title(const std::string &value)
Sets the current dialog title text.
Definition: file_dialog.hpp:59
file_dialog & set_save_mode(bool value)
Sets the dialog's behavior on non-existent file name inputs.
std::string path() const
Gets the current file selection.
Main class to show messages to the user.
Definition: message.hpp:36
bool show(const unsigned auto_close_time=0)
Shows the window.
int selected_index() const
Returns the selected item index after displaying.
void set_selected_index(int index)
Sets the initially selected item index (-1 by default).
void show(gui::TEXTBOX_MODE mode, const std::string &label, const std::string &check_label, bool checked, game_display &gui)
std::vector< team > & get_teams()
void show_objectives() const
statistics_t & statistics()
events::mouse_handler & get_mouse_handler_base() override
Get a reference to a mouse handler member a derived class uses.
game_state & gamestate()
const gamemap & get_map() const
void refresh_objectives() const
Reevaluate [show_if] conditions and build a new objectives string.
void notify_event(const std::string &name, const config &data)
Definition: manager.cpp:144
static plugins_manager * get()
Definition: manager.cpp:58
bool show_theme_dialog()
std::set< std::string > & encountered_units()
void add_alias(const std::string &alias, const std::string &command)
void write_preferences()
static prefs & get()
void set_message_private(bool value)
void set_show_fps(bool value)
bool confirm_no_moves()
std::string login()
bool empty() const
Is it empty?
static config get_recall(const std::string &unit_id, const map_location &loc, const map_location &from)
static config get_auto_shroud(bool turned_on)
Records that the player has toggled automatic shroud updates.
static config get_recruit(const std::string &type_id, const map_location &loc, const map_location &from)
static config get_update_shroud()
Records that the player has manually updated fog/shroud.
void add_rename(const std::string &name, const map_location &loc)
Definition: replay.cpp:279
void add_label(const terrain_label *)
Definition: replay.cpp:258
void speak(const config &cfg)
Definition: replay.cpp:336
void clear_labels(const std::string &, bool)
Definition: replay.cpp:269
static synced_state get_synced_state()
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
bool empty() const
Definition: tstring.hpp:195
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
void set_action_bonus_count(const int count)
Definition: team.hpp:205
int side() const
Definition: team.hpp:180
int recall_cost() const
Definition: team.hpp:185
bool auto_shroud_updates() const
Definition: team.hpp:329
const std::string & team_name() const
Definition: team.hpp:287
int action_bonus_count() const
Definition: team.hpp:204
int gold() const
Definition: team.hpp:181
static color_t get_side_color(int side)
Definition: team.cpp:949
recall_list_manager & recall_list()
Definition: team.hpp:206
To store label data Class implements logic for rendering.
Definition: label.hpp:111
const t_string & text() const
Definition: label.hpp:139
int turn() const
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_iterator begin()
Definition: map.hpp:418
@ NUM_GENDERS
Definition: race.hpp:28
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:1265
const std::vector< const unit_type * > types_list() const
Definition: types.hpp:397
const unit_type_map & types() const
Definition: types.hpp:396
A single unit type that the player may recruit.
Definition: types.hpp:43
const std::string & id() const
The id for this unit_type.
Definition: types.hpp:141
This class represents a single unit of a specific type.
Definition: unit.hpp:133
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
static vconfig empty_vconfig()
Definition: variable.cpp:141
Various functions related to the creation of units (recruits, recalls, and placed units).
static void print(std::stringstream &sstr, const std::string &queue, const std::string &id)
Definition: fire_event.cpp:30
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:1029
int w
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
void throw_quit_game_exception()
int dispatch(lua_State *L)
static std::string _(const char *str)
Definition: gettext.hpp:93
bool user_end_turn() const
Check whether the user ended their turn.
Definition: unit.hpp:785
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:380
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:403
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1397
bool has_moved() const
Checks if this unit has moved.
Definition: unit.hpp:1352
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1434
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1322
const map_location & get_goto() const
The map location to which this unit is moving over multiple turns, if any.
Definition: unit.hpp:1428
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:201
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: menu_events.cpp:84
#define LOG_NG
Definition: menu_events.cpp:85
const std::set< std::string > get_recruits(int side, const map_location &recruit_loc)
Gets the recruitable units from a side's leaders' personal recruit lists who can recruit on or from a...
Definition: create.cpp:60
game_events::pump_result_t get_village(const map_location &loc, int side, bool *action_timebonus, bool fire_event)
Makes it so the village at the given location is owned by the given side.
Definition: move.cpp:221
std::vector< unit_const_ptr > get_recalls(int side, const map_location &recall_loc)
Gets the recallable units for a side, restricted by that side's leaders' personal abilities to recall...
Definition: create.cpp:160
std::size_t move_unit_and_record(const std::vector< map_location > &steps, bool continued_move, bool *interrupted)
Wrapper around the other overload.
Definition: move.cpp:1431
std::string find_recall_location(const int side, map_location &recall_location, map_location &recall_from, const unit &unit_recall)
Finds a location on which to recall unit_recall.
Definition: create.cpp:330
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition: chrono.hpp:57
CURSOR_TYPE get()
Definition: cursor.cpp:216
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
Handling of system events.
std::string get_legacy_editor_dir()
const std::string map_extension
Definition: filesystem.hpp:79
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
const color_t LABEL_COLOR
std::string private_message
Game configuration data as global variables.
Definition: build_info.cpp:61
std::string path
Definition: filesystem.cpp:92
bool ignore_replay_errors
Definition: game_config.cpp:89
const bool & debug
Definition: game_config.cpp:95
bool disable_autosave
Definition: game_config.cpp:92
const std::string observer_team_name
observer team name used for observer team chat
Definition: filesystem.cpp:102
void set_debug(bool new_debug)
Definition: game_config.cpp:97
void show_transient_error_message(const std::string &message, const std::string &image, const bool message_use_markup)
Shows a transient error message to the user.
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:148
retval
Default window/dialog return values.
Definition: retval.hpp:30
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
General purpose widgets.
@ TEXTBOX_MESSAGE
@ TEXTBOX_SEARCH
@ TEXTBOX_COMMAND
void show_help(const std::string &show_topic)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:140
void show_terrain_description(const terrain_type &t)
Definition: help.cpp:77
void show_unit_description(const unit &u)
Definition: help.cpp:71
void flush_cache()
Purges all image caches.
Definition: picture.cpp:200
logger & err()
Definition: log.cpp:307
std::string tag(std::string_view tag, Args &&... data)
Wraps the given data in the specified formatting tag.
Definition: markup.hpp:50
void send_to_server(const config &data)
Attempts to send given data to server if a connection is open.
bool logged_in_as_moderator()
Gets whether the currently logged-in user is a moderator.
game_events::manager * game_events
Definition: resources.cpp:24
replay * recorder
Definition: resources.cpp:28
void flush_cache()
Definition: sound.cpp:196
bool ci_search(const std::string &s1, const std::string &s2)
Definition: gettext.cpp:565
static std::string sgettext(const char *str)
Definition: gettext.hpp:62
t_string recruit_message(const std::string &type_id, map_location &target_hex, map_location &recruited_from, team &current_team)
Definition: helper.cpp:170
constexpr auto transform
Definition: ranges.hpp:41
@ STRIP_SPACES
REMOVE_EMPTY: remove empty elements.
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:178
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
const std::string & gender_string(unit_race::GENDER gender)
Definition: race.cpp:138
Replay control code.
static config unit_name(const unit *u)
Definition: reports.cpp:161
Thrown when a lexical_cast fails.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
save_id_matches(const std::string &save_id)
bool operator()(const team &t) const
An exception object used when an IO error occurs.
Definition: filesystem.hpp:67
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: location.hpp:110
int wml_y() const
Definition: location.hpp:184
static const map_location & null_location()
Definition: location.hpp:102
int wml_x() const
Definition: location.hpp:183
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:142
std::vector< map_location > & steps
Definition: pathfind.hpp:187
Object which contains all the possible locations a unit can move to, with associated best routes to t...
Definition: pathfind.hpp:73
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
Object which temporarily resets a unit's movement.
Definition: unit.hpp:2144
ONLY IF whiteboard is currently active, applies the planned unit map for the duration of the struct's...
Definition: manager.hpp:274
Applies the planned unit map for the duration of the struct's life.
Definition: manager.hpp:253
const std::string & gamedata
mock_char c
static map_location::direction n
#define DBG_WB
Definition: typedefs.hpp:28
unit_type_data unit_types
Definition: types.cpp:1504
Various functions that implement the undoing (and redoing) of in-game commands.
#define e
#define h