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