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