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