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