The Battle for Wesnoth  1.19.0-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"
37 #include "preferences/game.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"
69 #include "preferences/display.hpp"
70 #include "replay.hpp"
71 #include "replay_controller.hpp"
72 #include "replay_helper.hpp"
73 #include "resources.hpp"
74 #include "savegame.hpp"
77 #include "synced_context.hpp"
78 #include "units/unit.hpp"
79 #include "units/types.hpp"
80 #include "whiteboard/manager.hpp"
81 #include "sound.hpp"
82 
83 static lg::log_domain log_engine("engine");
84 #define ERR_NG LOG_STREAM(err, log_engine)
85 #define LOG_NG LOG_STREAM(info, log_engine)
86 
87 namespace events
88 {
90  : gui_(gui)
91  , pc_(pc)
92  , game_config_(game_config_manager::get()->game_config())
93  , textbox_info_()
94  , last_search_()
95  , last_search_hit_()
96 {
97 }
98 
100 {
101 }
102 
104 {
105  return pc_.gamestate();
106 }
107 
109 {
110  return gamestate().gamedata_;
111 }
112 
114 {
115  return gamestate().board_;
116 }
117 
119 {
120  return textbox_info_;
121 }
122 
124 {
125  if(!gamestate().lua_kernel_) {
126  return;
127  }
128 
131 }
132 
134 {
135  gui2::dialogs::statistics_dialog::display(pc_.statistics(), board().get_team(side_num));
136 }
137 
139 {
141 }
142 
144 {
145  int selected_side;
146 
147  if(gui2::dialogs::game_stats::execute(board(), gui_->viewing_team(), selected_side)) {
148  gui_->scroll_to_leader(selected_side);
149  }
150 }
151 
153 {
154  const std::string& input_name
156 
158 
159  dlg.set_title(_("Save Map As"))
160  .set_save_mode(true)
161  .set_path(input_name)
162  .set_extension(".map");
163 
164  if(!dlg.show()) {
165  return;
166  }
167 
168  try {
170  gui2::show_transient_message("", _("Map saved."));
171  } catch(const filesystem::io_exception& e) {
172  utils::string_map symbols;
173  symbols["msg"] = e.what();
174  const std::string msg = VGETTEXT("Could not save the map: $msg", symbols);
176  }
177 }
178 
180 {
181  gui2::dialogs::preferences_dialog::display();
182  // Needed after changing fullscreen/windowed mode or display resolution
183  gui_->queue_rerender();
184 }
185 
187 {
188  config c;
189  c["name"] = "prototype of chat log";
190  gui2::dialogs::chat_log::display(vconfig(c), *resources::recorder);
191  // std::string text = resources::recorder->build_chat_log();
192  // gui::show_dialog(*gui_,nullptr,_("Chat Log"),"",gui::CLOSE_ONLY,nullptr,nullptr,"",&text);
193 }
194 
196 {
197  help::show_help();
198 }
199 
201 {
203  ? board().is_observer()
204  ? _("Send to observers only")
205  : _("Send to allies only")
207 }
208 
210 {
212  speak();
213 }
214 
216 {
218  speak();
219 }
220 
222 {
223  if(board().is_observer()) {
224  return !gui_->observers().empty();
225  }
226 
227  for(std::size_t n = 0; n != pc_.get_teams().size(); ++n) {
228  if(n != gui_->viewing_team() && pc_.get_teams()[gui_->viewing_team()].team_name() == pc_.get_teams()[n].team_name()
229  && pc_.get_teams()[n].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), true, true,
451 
452  if(!success) {
453  ERR_NG << "menu_handler::recall(): Unit does not exist in the recall list.";
454  }
455  }
456 }
457 
458 // Highlights squares that an enemy could move to on their turn, showing how many can reach each square.
459 void menu_handler::show_enemy_moves(bool ignore_units, int side_num)
460 {
461  wb::future_map future; // use unit positions as if all planned actions were executed
462 
463  mouse_handler& mh = pc_.get_mouse_handler_base();
464  const map_location& hex_under_mouse = mh.hovered_hex();
465 
466  gui_->unhighlight_reach();
467 
468  // Compute enemy movement positions
469  for(auto& u : pc_.get_units()) {
470  bool invisible = u.invisible(u.get_location());
471 
472  if(board().get_team(side_num).is_enemy(u.side()) && !gui_->fogged(u.get_location()) && !u.incapacitated()
473  && !invisible) {
474  const unit_movement_resetter move_reset(u);
475  const pathfind::paths& path
476  = pathfind::paths(u, false, true, pc_.get_teams()[gui_->viewing_team()], 0, false, ignore_units);
477 
478  gui_->highlight_another_reach(path, hex_under_mouse);
479  }
480 
481  // Need to recompute ellipses for highlighted enemy units
482  gui_->invalidate(u.get_location());
483  }
484 
485  // Find possible unit (no matter whether friend or foe) under the
486  // mouse cursor.
487  const bool selected_hex_has_unit = mh.hex_hosts_unit(hex_under_mouse);
488 
489  if(selected_hex_has_unit) {
490  // At this point, a single pixel move would remove the enemy
491  // [best possible] movements hex tiles highlights, so some
492  // prevention on normal unit mouseover movement highlight
493  // has to be toggled temporarily.
495  }
496 }
497 
498 void menu_handler::toggle_shroud_updates(int side_num)
499 {
500  team& current_team = board().get_team(side_num);
501  bool auto_shroud = current_team.auto_shroud_updates();
502  // If we're turning automatic shroud updates on, then commit all moves
503  if(!auto_shroud) {
504  update_shroud_now(side_num);
505  }
506 
507  // Toggle the setting and record this.
509 }
510 
511 void menu_handler::update_shroud_now(int /* side_num */)
512 {
514 }
515 
516 // Helpers for menu_handler::end_turn()
517 namespace
518 {
519 /** Returns true if @a side_num has at least one living unit. */
520 bool units_alive(int side_num, const unit_map& units)
521 {
522  for(auto& unit : units) {
523  if(unit.side() == side_num) {
524  return true;
525  }
526  }
527  return false;
528 }
529 
530 /** Returns true if @a side_num has at least one unit that can still move. */
531 bool partmoved_units(
532  int side_num, const unit_map& units, const game_board& board, const std::shared_ptr<wb::manager>& whiteb)
533 {
534  for(auto& unit : units) {
535  if(unit.side() == side_num) {
536  // @todo whiteboard should take into consideration units that have
537  // a planned move but can still plan more movement in the same turn
538  if(board.unit_can_move(unit) && !unit.user_end_turn() && (!whiteb || !whiteb->unit_has_actions(&unit)))
539  return true;
540  }
541  }
542  return false;
543 }
544 
545 /**
546  * Returns true if @a side_num has at least one unit that (can but) has not moved.
547  */
548 bool unmoved_units(
549  int side_num, const unit_map& units, const game_board& board, const std::shared_ptr<wb::manager>& whiteb)
550 {
551  for(auto& unit : units) {
552  if(unit.side() == side_num) {
553  if(board.unit_can_move(unit) && !unit.has_moved() && !unit.user_end_turn()
554  && (!whiteb || !whiteb->unit_has_actions(&unit))) {
555  return true;
556  }
557  }
558  }
559  return false;
560 }
561 
562 } // end anon namespace
563 
564 bool menu_handler::end_turn(int side_num)
565 {
566  if(!gamedata().allow_end_turn()) {
567  t_string reason = gamedata().cannot_end_turn_reason();
568  if(reason.empty()) {
569  reason = _("You cannot end your turn yet!");
570  }
571  gui2::show_transient_message("", reason);
572  return false;
573  }
574 
575  std::size_t team_num = static_cast<std::size_t>(side_num - 1);
576  if(team_num < pc_.get_teams().size() && pc_.get_teams()[team_num].no_turn_confirmation()) {
577  // Skip the confirmations that follow.
578  }
579  // Ask for confirmation if the player hasn't made any moves.
580  else if(preferences::confirm_no_moves() && !pc_.get_undo_stack().player_acted()
581  && (!pc_.get_whiteboard() || !pc_.get_whiteboard()->current_side_has_actions())
582  && units_alive(side_num, pc_.get_units())) {
583  const int res = gui2::show_message("",
584  _("You have not started your turn yet. Do you really want to end your turn?"),
586  if(res == gui2::retval::CANCEL) {
587  return false;
588  }
589  }
590  // Ask for confirmation if units still have some movement left.
591  else if(preferences::yellow_confirm() && partmoved_units(side_num, pc_.get_units(), board(), pc_.get_whiteboard())) {
592  const int res = gui2::show_message("",
593  _("Some units have movement left. Do you really want to end your turn?"),
595  if(res == gui2::retval::CANCEL) {
596  return false;
597  }
598  }
599  // Ask for confirmation if units still have all movement left.
600  else if(preferences::green_confirm() && unmoved_units(side_num, pc_.get_units(), board(), pc_.get_whiteboard())) {
601  const int res = gui2::show_message("",
602  _("Some units have not moved. Do you really want to end your turn?"),
604  if(res == gui2::retval::CANCEL) {
605  return false;
606  }
607  }
608 
609  // Auto-execute remaining whiteboard planned actions
610  // Only finish turn if they all execute successfully, i.e. no ambush, etc.
611  if(pc_.get_whiteboard() && !pc_.get_whiteboard()->allow_end_turn()) {
612  return false;
613  }
614 
615  return true;
616 }
617 
618 void menu_handler::goto_leader(int side_num)
619 {
620  unit_map::const_iterator i = pc_.get_units().find_leader(side_num);
621  const display_context& dc = gui_->get_disp_context();
622  if(i != pc_.get_units().end() && i->is_visible_to_team(dc.get_team(gui_->viewing_side()), 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_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(), pc_.get_teams()[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(), pc_.get_teams()[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_side());
824  }
825  const terrain_label* res = gui_->labels().set_label(loc, label, gui_->viewing_team(), team_name, color);
826  if(res) {
828  }
829  }
830 }
831 
832 void menu_handler::clear_labels()
833 {
834  if(gui_->team_valid() && !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 {
991  gui_->invalidate_all(); // TODO can fewer tiles be invalidated?
992 }
993 
994 void menu_handler::toggle_grid()
995 {
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", preferences::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 = preferences::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"] = preferences::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_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"] = pc_.get_teams()[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(!pc_.get_teams()[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_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", preferences::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", preferences::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", preferences::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", preferences::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", preferences::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  }
1569  menu_handler_.textbox_info_.close();
1570 }
1571 
1572 void console_handler::do_terrain()
1573 {
1574  // :terrain [<terrain type> [both|base|overlay]]
1575  const std::string terrain_type = get_arg(1);
1576  const std::string mode_str = get_arg(2);
1577 
1578  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
1579  const map_location& loc = mousehandler.get_last_hex();
1580 
1581  synced_context::run_and_throw("debug_terrain",
1582  config {
1583  "x", loc.wml_x(),
1584  "y", loc.wml_y(),
1585  "terrain_type", terrain_type,
1586  "mode_str", mode_str,
1587  }
1588  );
1589 }
1590 
1591 void console_handler::do_idle()
1592 {
1593  // :idle [<side> [on/off]]
1594  const std::string side_s = get_arg(1);
1595  const std::string action = get_arg(2);
1596  // default to the current side if empty
1597  const unsigned int side = side_s.empty() ? team_num_ : lexical_cast_default<unsigned int>(side_s);
1598 
1599  if(side < 1 || side > menu_handler_.pc_.get_teams().size()) {
1600  utils::string_map symbols;
1601  symbols["side"] = side_s;
1602  command_failed(VGETTEXT("Can't idle invalid side: '$side'.", symbols));
1603  return;
1604  } else if(menu_handler_.board().get_team(side).is_network()) {
1605  utils::string_map symbols;
1606  symbols["side"] = std::to_string(side);
1607  command_failed(VGETTEXT("Can't idle networked side: '$side'.", symbols));
1608  return;
1609  } else if(menu_handler_.board().get_team(side).is_local_ai()) {
1610  utils::string_map symbols;
1611  symbols["side"] = std::to_string(side);
1612  command_failed(VGETTEXT("Can't idle local ai side: '$side'.", symbols));
1613  return;
1614  } else if(menu_handler_.board().get_team(side).is_local_human()) {
1615  if(menu_handler_.board().get_team(side).is_idle() ? action == " on" : action == " off") {
1616  return;
1617  }
1618  // toggle the proxy controller between idle / non idle
1619  menu_handler_.board().get_team(side).toggle_idle();
1620  if(team_num_ == side) {
1621  if(playsingle_controller* psc = dynamic_cast<playsingle_controller*>(&menu_handler_.pc_)) {
1622  psc->set_player_type_changed();
1623  }
1624  }
1625  }
1626  menu_handler_.textbox_info_.close();
1627 }
1628 
1629 void console_handler::do_theme()
1630 {
1632 }
1633 
1635 {
1636  save_id_matches(const std::string& save_id)
1637  : save_id_(save_id)
1638  {
1639  }
1640 
1641  bool operator()(const team& t) const
1642  {
1643  return t.save_id() == save_id_;
1644  }
1645 
1646  std::string save_id_;
1647 };
1648 
1649 void console_handler::do_control()
1650 {
1651  // :control <side> <nick>
1652  if(!menu_handler_.pc_.is_networked_mp()) {
1653  return;
1654  }
1655 
1656  const std::string side = get_arg(1);
1657  const std::string player = get_arg(2);
1658  if(player.empty()) {
1659  command_failed_need_arg(2);
1660  return;
1661  }
1662 
1663  unsigned int side_num;
1664  try {
1665  side_num = lexical_cast<unsigned int>(side);
1666  } catch(const bad_lexical_cast&) {
1667  const auto& teams = menu_handler_.pc_.get_teams();
1668  const auto it_t = std::find_if(teams.begin(), teams.end(), save_id_matches(side));
1669 
1670  if(it_t == teams.end()) {
1671  utils::string_map symbols;
1672  symbols["side"] = side;
1673  command_failed(VGETTEXT("Can't change control of invalid side: '$side'.", symbols));
1674  return;
1675  } else {
1676  side_num = it_t->side();
1677  }
1678  }
1679 
1680  if(side_num < 1 || side_num > menu_handler_.pc_.get_teams().size()) {
1681  utils::string_map symbols;
1682  symbols["side"] = side;
1683  command_failed(VGETTEXT("Can't change control of out-of-bounds side: '$side'.", symbols));
1684  return;
1685  }
1686 
1687  menu_handler_.request_control_change(side_num, player);
1688  menu_handler_.textbox_info_.close();
1689 }
1690 
1691 void console_handler::do_controller()
1692 {
1693  const std::string side = get_arg(1);
1694  unsigned int side_num;
1695  try {
1696  side_num = lexical_cast<unsigned int>(side);
1697  } catch(const bad_lexical_cast&) {
1698  utils::string_map symbols;
1699  symbols["side"] = side;
1700  command_failed(VGETTEXT("Can't query control of invalid side: '$side'.", symbols));
1701  return;
1702  }
1703 
1704  if(side_num < 1 || side_num > menu_handler_.pc_.get_teams().size()) {
1705  utils::string_map symbols;
1706  symbols["side"] = side;
1707  command_failed(VGETTEXT("Can't query control of out-of-bounds side: '$side'.", symbols));
1708  return;
1709  }
1710 
1711  std::string report = side_controller::get_string(menu_handler_.board().get_team(side_num).controller());
1712  if(!menu_handler_.board().get_team(side_num).is_proxy_human()) {
1713  report += " (" + side_proxy_controller::get_string(menu_handler_.board().get_team(side_num).proxy_controller()) + ")";
1714  }
1715 
1716  if(menu_handler_.board().get_team(side_num).is_network()) {
1717  report += " (networked)";
1718  }
1719 
1720  print(get_cmd(), report);
1721 }
1722 
1723 void console_handler::do_clear()
1724 {
1725  menu_handler_.gui_->get_chat_manager().clear_chat_messages();
1726 }
1727 
1728 void console_handler::do_foreground()
1729 {
1730  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_FOREGROUND);
1731  menu_handler_.gui_->invalidate_all();
1732 }
1733 
1734 void console_handler::do_layers()
1735 {
1736  display& disp = *(menu_handler_.gui_);
1737 
1738  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
1739  const map_location& loc = mousehandler.get_last_hex();
1740 
1741  //
1742  // It's possible to invoke this dialog even if loc isn't a valid hex. I'm not sure
1743  // exactly how that happens, but it does seem to occur when moving the mouse outside
1744  // the window to the menu bar. Not sure if there's a bug when the set-last-hex code
1745  // in that case, but this check at least ensures the dialog is only ever shown for a
1746  // valid, on-map location. Otherwise, an assertion gets thrown.
1747  //
1748  // -- vultraz, 2017-09-21
1749  //
1750  if(disp.get_map().on_board_with_border(loc)) {
1751  gui2::dialogs::terrain_layers::display(disp, loc);
1752  }
1753 }
1754 
1755 void console_handler::do_fps()
1756 {
1758 }
1759 
1760 void console_handler::do_benchmark()
1761 {
1762  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_BENCHMARK);
1763 }
1764 
1765 void console_handler::do_save()
1766 {
1767  menu_handler_.pc_.do_consolesave(get_data());
1768 }
1769 
1770 void console_handler::do_save_quit()
1771 {
1772  do_save();
1773  do_quit();
1774 }
1775 
1776 void console_handler::do_quit()
1777 {
1779 }
1780 
1781 void console_handler::do_ignore_replay_errors()
1782 {
1783  game_config::ignore_replay_errors = (get_data() != "off") ? true : false;
1784 }
1785 
1786 void console_handler::do_nosaves()
1787 {
1788  game_config::disable_autosave = (get_data() != "off") ? true : false;
1789 }
1790 
1791 void console_handler::do_next_level()
1792 {
1793  synced_context::run_and_throw("debug_next_level", config {"next_level", get_data()});
1794 }
1795 
1796 void console_handler::do_choose_level()
1797 {
1798  std::string tag = menu_handler_.pc_.get_classification().get_tagname();
1799  std::vector<std::string> options;
1800  std::string next;
1801  if(tag != "multiplayer") {
1802  for(const config& sc : menu_handler_.game_config_.child_range(tag)) {
1803  const std::string& id = sc["id"];
1804  options.push_back(id);
1805  if(id == menu_handler_.gamedata().next_scenario()) {
1806  next = id;
1807  }
1808  }
1809  } else {
1810  // find scenarios of multiplayer campaigns
1811  // (assumes that scenarios are ordered properly in the game_config)
1812  std::string scenario_id = menu_handler_.pc_.get_mp_settings().mp_scenario;
1813  if(auto this_scenario = menu_handler_.game_config_.find_child(tag, "id", scenario_id)) {
1814  std::string addon_id = this_scenario["addon_id"].str();
1815  for(const config& sc : menu_handler_.game_config_.child_range(tag)) {
1816  if(sc["addon_id"] == addon_id) {
1817  std::string id = sc["id"];
1818  options.push_back(id);
1819  if(id == menu_handler_.gamedata().next_scenario()) {
1820  next = id;
1821  }
1822  }
1823  }
1824  }
1825  }
1826  std::sort(options.begin(), options.end());
1827  int choice = std::distance(options.begin(), std::lower_bound(options.begin(), options.end(), next));
1828  {
1829  gui2::dialogs::simple_item_selector dlg(_("Choose Scenario (Debug!)"), "", options);
1830  dlg.set_selected_index(choice);
1831  dlg.show();
1832  choice = dlg.selected_index();
1833  }
1834 
1835  if(choice == -1) {
1836  return;
1837  }
1838 
1839  if(std::size_t(choice) < options.size()) {
1840  synced_context::run_and_throw("debug_next_level", config {"next_level", options[choice]});
1841  }
1842 }
1843 
1844 void console_handler::do_turn()
1845 {
1846  tod_manager& tod_man = menu_handler_.gamestate().tod_manager_;
1847 
1848  int turn = tod_man.turn() + 1;
1849  const std::string& data = get_data();
1850  if(!data.empty()) {
1851  turn = lexical_cast_default<int>(data, 1);
1852  }
1853  synced_context::run_and_throw("debug_turn", config {"turn", turn});
1854 }
1855 
1856 void console_handler::do_turn_limit()
1857 {
1858  int limit = get_data().empty() ? -1 : lexical_cast_default<int>(get_data(), 1);
1859  synced_context::run_and_throw("debug_turn_limit", config {"turn_limit", limit});
1860 }
1861 
1862 void console_handler::do_debug()
1863 {
1864  if(!menu_handler_.pc_.is_networked_mp() || game_config::mp_debug) {
1865  print(get_cmd(), _("Debug mode activated!"));
1866  game_config::set_debug(true);
1867  } else {
1868  command_failed(_("Debug mode not available in network games"));
1869  }
1870 }
1871 
1872 void console_handler::do_nodebug()
1873 {
1874  if(game_config::debug) {
1875  print(get_cmd(), _("Debug mode deactivated!"));
1876  game_config::set_debug(false);
1877  }
1878 }
1879 
1880 void console_handler::do_lua()
1881 {
1882  if(!menu_handler_.gamestate().lua_kernel_) {
1883  return;
1884  }
1885 
1886  synced_context::run_and_throw("debug_lua", config {"code", get_data()});
1887 }
1888 
1889 void console_handler::do_unsafe_lua()
1890 {
1891  if(!menu_handler_.gamestate().lua_kernel_) {
1892  return;
1893  }
1894 
1895  const int retval = gui2::show_message(_("WARNING! Unsafe Lua Mode"),
1896  _("Executing Lua code in in this manner opens your computer to potential security breaches from any "
1897  "malicious add-ons or other programs you may have installed.\n\n"
1898  "Do not continue unless you really know what you are doing."), gui2::dialogs::message::ok_cancel_buttons);
1899 
1900  if(retval == gui2::retval::OK) {
1901  print(get_cmd(), _("Unsafe mode enabled!"));
1902  menu_handler_.gamestate().lua_kernel_->load_package();
1903  }
1904 }
1905 
1906 void console_handler::do_custom()
1907 {
1908  preferences::set_custom_command(get_data());
1909 }
1910 
1911 void console_handler::do_set_alias()
1912 {
1913  const std::string data = get_data();
1914  const std::string::const_iterator j = std::find(data.begin(), data.end(), '=');
1915  const std::string alias(data.begin(), j);
1916  if(j != data.end()) {
1917  const std::string command(j + 1, data.end());
1918  if(!command.empty()) {
1919  register_alias(command, alias);
1920  } else {
1921  // "alias something=" deactivate this alias. We just set it
1922  // equal to itself here. Later preferences will filter empty alias.
1923  register_alias(alias, alias);
1924  }
1925  preferences::add_alias(alias, command);
1926  // directly save it for the moment, but will slow commands sequence
1928  } else {
1929  // "alias something" display its value
1930  // if no alias, will be "'something' = 'something'"
1931  const std::string command = chmap::get_actual_cmd(alias);
1932  print(get_cmd(), "'" + alias + "'" + " = " + "'" + command + "'");
1933  }
1934 }
1935 
1936 void console_handler::do_set_var()
1937 {
1938  const std::string data = get_data();
1939  if(data.empty()) {
1940  command_failed_need_arg(1);
1941  return;
1942  }
1943 
1944  const std::string::const_iterator j = std::find(data.begin(), data.end(), '=');
1945  if(j != data.end()) {
1946  const std::string name(data.begin(), j);
1947  const std::string value(j + 1, data.end());
1948  synced_context::run_and_throw("debug_set_var", config {"name", name, "value", value});
1949  } else {
1950  command_failed(_("Variable not found"));
1951  }
1952 }
1953 
1954 void console_handler::do_show_var()
1955 {
1956  gui2::show_transient_message("", menu_handler_.gamedata().get_variable_const(get_data()));
1957 }
1958 
1959 void console_handler::do_inspect()
1960 {
1962  gui2::dialogs::gamestate_inspector::display(
1963  menu_handler_.gamedata().get_variables(), *resources::game_events, menu_handler_.board());
1964 }
1965 
1966 void console_handler::do_control_dialog()
1967 {
1968  gui2::dialogs::mp_change_control::display(menu_handler_);
1969 }
1970 
1971 void console_handler::do_unit()
1972 {
1973  // prevent SIGSEGV due to attempt to set HP during a fight
1974  if(events::commands_disabled > 0) {
1975  return;
1976  }
1977 
1978  unit_map::iterator i = menu_handler_.current_unit();
1979  if(i == menu_handler_.pc_.get_units().end()) {
1980  utils::string_map symbols;
1981  symbols["unit"] = get_arg(1);
1982  command_failed(VGETTEXT(
1983  "Debug command 'unit: $unit' failed: no unit selected or hovered over.",
1984  symbols));
1985  return;
1986  }
1987 
1988  const map_location loc = i->get_location();
1989  const std::string data = get_data(1);
1990  std::vector<std::string> parameters = utils::split(data, '=', utils::STRIP_SPACES);
1991  if(parameters.size() < 2) {
1992  return;
1993  }
1994 
1995  if(parameters[0] == "alignment") {
1996  auto alignment = unit_alignments::get_enum(parameters[1]);
1997  if(!alignment) {
1998  utils::string_map symbols;
1999  symbols["alignment"] = get_arg(1);
2000  command_failed(VGETTEXT(
2001  "Invalid alignment: '$alignment', needs to be one of lawful, neutral, chaotic, or liminal.",
2002  symbols));
2003  return;
2004  }
2005  }
2006 
2007  synced_context::run_and_throw("debug_unit",
2008  config {
2009  "x", loc.wml_x(),
2010  "y", loc.wml_y(),
2011  "name", parameters[0],
2012  "value", parameters[1],
2013  }
2014  );
2015 }
2016 
2017 void console_handler::do_discover()
2018 {
2019  for(const unit_type_data::unit_type_map::value_type& i : unit_types.types()) {
2020  preferences::encountered_units().insert(i.second.id());
2021  }
2022 }
2023 
2024 void console_handler::do_undiscover()
2025 {
2026  const int res = gui2::show_message("Undiscover",
2027  _("Do you wish to clear all of your discovered units from help?"), gui2::dialogs::message::yes_no_buttons);
2028  if(res != gui2::retval::CANCEL) {
2030  }
2031 }
2032 
2033 /** Implements the (debug mode) console command that creates a unit. */
2034 void console_handler::do_create()
2035 {
2036  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
2037  const map_location& loc = mousehandler.get_last_hex();
2038  if(menu_handler_.pc_.get_map().on_board(loc)) {
2039  const unit_type* ut = unit_types.find(get_data());
2040  if(!ut) {
2041  command_failed(_("Invalid unit type"));
2042  return;
2043  }
2044 
2045  // Create the unit.
2046  create_and_place(*menu_handler_.gui_, menu_handler_.pc_.get_map(), menu_handler_.pc_.get_units(), loc, *ut);
2047  } else {
2048  command_failed(_("Invalid location"));
2049  }
2050 }
2051 
2052 void console_handler::do_fog()
2053 {
2054  synced_context::run_and_throw("debug_fog", config());
2055 }
2056 
2057 void console_handler::do_shroud()
2058 {
2059  synced_context::run_and_throw("debug_shroud", config());
2060 }
2061 
2062 void console_handler::do_gold()
2063 {
2064  synced_context::run_and_throw("debug_gold", config {"gold", lexical_cast_default<int>(get_data(), 1000)});
2065 }
2066 
2067 void console_handler::do_event()
2068 {
2069  synced_context::run_and_throw("debug_event", config {"eventname", get_data()});
2070 }
2071 
2072 void console_handler::do_toggle_draw_coordinates()
2073 {
2074  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_COORDINATES);
2075  menu_handler_.gui_->invalidate_all();
2076 }
2077 void console_handler::do_toggle_draw_terrain_codes()
2078 {
2079  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_TERRAIN_CODES);
2080  menu_handler_.gui_->invalidate_all();
2081 }
2082 
2083 void console_handler::do_toggle_draw_num_of_bitmaps()
2084 {
2085  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_NUM_BITMAPS);
2086  menu_handler_.gui_->invalidate_all();
2087 }
2088 
2089 void console_handler::do_toggle_whiteboard()
2090 {
2091  if(const std::shared_ptr<wb::manager>& whiteb = menu_handler_.pc_.get_whiteboard()) {
2092  whiteb->set_active(!whiteb->is_active());
2093  if(whiteb->is_active()) {
2094  print(get_cmd(), _("Planning mode activated!"));
2095  whiteb->print_help_once();
2096  } else {
2097  print(get_cmd(), _("Planning mode deactivated!"));
2098  }
2099  }
2100 }
2101 
2102 void console_handler::do_whiteboard_options()
2103 {
2104  if(menu_handler_.pc_.get_whiteboard()) {
2105  menu_handler_.pc_.get_whiteboard()->options_dlg();
2106  }
2107 }
2108 
2109 void menu_handler::do_ai_formula(const std::string& str, int side_num, mouse_handler& /*mousehandler*/)
2110 {
2111  try {
2112  add_chat_message(std::time(nullptr), "wfl", 0, ai::manager::get_singleton().evaluate_command(side_num, str));
2113  } catch(const wfl::formula_error&) {
2114  } catch(...) {
2115  add_chat_message(std::time(nullptr), "wfl", 0, "UNKNOWN ERROR IN FORMULA: "+utils::get_unknown_exception_type());
2116  }
2117 }
2118 
2119 void menu_handler::user_command()
2120 {
2121  textbox_info_.show(gui::TEXTBOX_COMMAND, translation::sgettext("prompt^Command:"), "", false, *gui_);
2122 }
2123 
2124 void menu_handler::request_control_change(int side_num, const std::string& player)
2125 {
2126  std::string side = std::to_string(side_num);
2127  if(board().get_team(side_num).is_local_human() && player == preferences::login()) {
2128  // this is already our side.
2129  return;
2130  } else {
2131  // The server will (or won't because we aren't allowed to change the controller)
2132  // send us a [change_controller] back, which we then handle in playturn.cpp
2133  pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", player}});
2134  }
2135 }
2136 
2138 {
2139  for(const std::string& command : utils::split(preferences::custom_command(), ';')) {
2140  do_command(command);
2141  }
2142 }
2143 
2144 void menu_handler::ai_formula()
2145 {
2146  if(!pc_.is_networked_mp()) {
2147  textbox_info_.show(gui::TEXTBOX_AI, translation::sgettext("prompt^Command:"), "", false, *gui_);
2148  }
2149 }
2150 
2151 void menu_handler::clear_messages()
2152 {
2153  gui_->get_chat_manager().clear_chat_messages(); // also clear debug-messages and WML-error-messages
2154 }
2155 
2157 {
2158  pc_.send_to_wesnothd(cfg);
2159 }
2160 
2161 } // 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
Abstract class for exposing game data that doesn't depend on the GUI, however which for historical re...
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
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:81
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:116
@ ONSCREEN
Definition: display.hpp:499
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition: display.hpp:962
@ DEBUG_BENCHMARK
Toggle to continuously redraw the whole map.
Definition: display.hpp:974
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition: display.hpp:968
@ DEBUG_FOREGROUND
Separates background and foreground terrain layers.
Definition: display.hpp:971
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition: display.hpp:965
const gamemap & get_map() const
Definition: display.hpp:99
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2320
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:89
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)
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)
pathfind::marked_route get_route(const unit *un, map_location go_to, team &team) const
Game board class.
Definition: game_board.hpp:46
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 the default file extension 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.
A simple one-column listbox with OK and Cancel buttons.
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).
This shows the debug-mode dialog to create new units on the map.
Definition: unit_create.hpp:47
unit_race::GENDER gender()
Gender choice from the user.
Definition: unit_create.hpp:64
bool no_choice() const
Whether the user actually chose a unit type or not.
Definition: unit_create.hpp:58
std::string variation() const
Variation choice from the user.
Definition: unit_create.hpp:70
const std::string & choice() const
Unit type choice from the user.
Definition: unit_create.hpp:52
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 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:293
void add_label(const terrain_label *)
Definition: replay.cpp:272
void speak(const config &cfg)
Definition: replay.cpp:350
void clear_labels(const std::string &, bool)
Definition: replay.cpp:283
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 recall_cost() const
Definition: team.hpp:179
bool auto_shroud_updates() const
Definition: team.hpp:324
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:27
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:1266
const unit_type_map & types() const
Definition: types.hpp:393
A single unit type that the player may recruit.
Definition: types.hpp:43
const std::string & id() const
The id for this unit_type.
Definition: types.hpp: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:248
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:968
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:1357
bool has_moved() const
Checks if this unit has moved.
Definition: unit.hpp:1312
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1394
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1282
const map_location & get_goto() const
The map location to which this unit is moving over multiple turns, if any.
Definition: unit.hpp:1388
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:209
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:207
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: menu_events.cpp:84
#define LOG_NG
Definition: menu_events.cpp:85
std::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:1229
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:329
std::string get_user_data_dir()
Definition: filesystem.cpp:870
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:60
std::string path
Definition: filesystem.cpp:83
bool ignore_replay_errors
Definition: game_config.cpp:84
const bool & debug
Definition: game_config.cpp:91
bool disable_autosave
Definition: game_config.cpp:88
const std::string observer_team_name
observer team name used for observer team chat
Definition: filesystem.cpp:93
void set_debug(bool new_debug)
Definition: game_config.cpp:93
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:216
logger & err()
Definition: log.cpp:302
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.
void set_message_private(bool value)
Definition: game.cpp:835
void set_custom_command(const std::string &command)
Definition: game.cpp:928
bool show_theme_dialog()
Definition: display.cpp:61
void set_grid(bool ison)
Definition: general.cpp:570
void set_show_fps(bool value)
Definition: general.cpp:892
void write_preferences()
Definition: general.cpp:141
optional_const_config get_alias()
Definition: general.cpp:940
std::set< std::string > & encountered_units()
Definition: game.cpp:913
bool confirm_no_moves()
Definition: game.cpp:955
void add_alias(const std::string &alias, const std::string &command)
Definition: general.cpp:933
std::string custom_command()
Definition: game.cpp:923
bool green_confirm()
Definition: game.cpp:944
bool message_private()
Definition: game.cpp:830
bool ellipses()
Definition: general.cpp:555
void set_ellipses(bool ison)
Definition: general.cpp:560
std::string login()
bool grid()
Definition: general.cpp:565
bool show_fps()
Definition: general.cpp:887
const config & options()
Definition: game.cpp:552
bool yellow_confirm()
Definition: game.cpp:950
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
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
@ 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:194
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:64
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 std::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:2028
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:1485
Various functions that implement the undoing (and redoing) of in-game commands.
#define e
#define h
#define a