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