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