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