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