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