The Battle for Wesnoth  1.19.2+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"
75 #include "synced_context.hpp"
76 #include "units/unit.hpp"
77 #include "units/types.hpp"
78 #include "whiteboard/manager.hpp"
79 #include "sound.hpp"
80 
81 static lg::log_domain log_engine("engine");
82 #define ERR_NG LOG_STREAM(err, log_engine)
83 #define LOG_NG LOG_STREAM(info, log_engine)
84 
85 namespace events
86 {
88  : gui_(gui)
89  , pc_(pc)
90  , game_config_(game_config_manager::get()->game_config())
91  , textbox_info_()
92  , last_search_()
93  , last_search_hit_()
94 {
95 }
96 
98 {
99 }
100 
102 {
103  return pc_.gamestate();
104 }
105 
107 {
108  return gamestate().gamedata_;
109 }
110 
112 {
113  return gamestate().board_;
114 }
115 
117 {
118  return textbox_info_;
119 }
120 
122 {
123  if(!gamestate().lua_kernel_) {
124  return;
125  }
126 
129 }
130 
132 {
133  gui2::dialogs::statistics_dialog::display(pc_.statistics(), board().get_team(side_num));
134 }
135 
137 {
139 }
140 
142 {
143  int selected_side;
144 
145  if(gui2::dialogs::game_stats::execute(board(), gui_->viewing_team(), selected_side)) {
146  gui_->scroll_to_leader(selected_side);
147  }
148 }
149 
151 {
152  const std::string& input_name
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  // std::string text = resources::recorder->build_chat_log();
190  // gui::show_dialog(*gui_,nullptr,_("Chat Log"),"",gui::CLOSE_ONLY,nullptr,nullptr,"",&text);
191 }
192 
194 {
195  help::show_help();
196 }
197 
199 {
201  ? board().is_observer()
202  ? _("Send to observers only")
203  : _("Send to allies only")
204  : "", prefs::get().message_private(), *gui_);
205 }
206 
208 {
210  speak();
211 }
212 
214 {
216  speak();
217 }
218 
220 {
221  if(board().is_observer()) {
222  return !gui_->observers().empty();
223  }
224 
225  for(std::size_t n = 0; n != pc_.get_teams().size(); ++n) {
226  if(n != gui_->viewing_team() && pc_.get_teams()[gui_->viewing_team()].team_name() == pc_.get_teams()[n].team_name()
227  && pc_.get_teams()[n].is_network()) {
228  return true;
229  }
230  }
231 
232  return false;
233 }
234 
235 void menu_handler::recruit(int side_num, const map_location& last_hex)
236 {
237  std::map<const unit_type*, t_string> sample_units;
238 
239  std::set<std::string> recruits = actions::get_recruits(side_num, last_hex);
240 
241  std::vector<t_string> unknown_units;
242  for(const auto& recruit : recruits) {
244  if(!type) {
245  ERR_NG << "could not find unit '" << recruit << "'";
246  unknown_units.emplace_back(recruit);
247  continue;
248  }
249 
250  map_location ignored;
251  map_location recruit_hex = last_hex;
252  sample_units[type] = (can_recruit(type->id(), side_num, recruit_hex, ignored));
253  }
254  if(!unknown_units.empty()) {
255  auto unknown_ids = utils::format_conjunct_list("", unknown_units);
256  // TRANSLATORS: An error that should never happen, might happen when loading an old savegame. If there are
257  // any units that the player can recruit then their standard recruitment dialog will be shown after this
258  // error message, otherwise they'll get the "You have no units available to recruit." error after this one.
259  const auto message = VNGETTEXT("Error: there’s an unknown unit type on your recruit list: $unknown_ids",
260  "Error: there are several unknown unit types on your recruit list: $unknown_ids",
261  unknown_units.size(),
262  utils::string_map { { "unknown_ids", unknown_ids }});
263  gui2::show_transient_message("", message);
264  }
265 
266  if(sample_units.empty()) {
267  gui2::show_transient_message("", _("You have no units available to recruit."));
268  return;
269  }
270 
271  gui2::dialogs::unit_recruit dlg(sample_units, board().get_team(side_num));
272 
273  if(dlg.show()) {
274  map_location recruit_hex = last_hex;
275  const unit_type *type = dlg.get_selected_unit_type();
276  if (!type) {
277  gui2::show_transient_message("", _("No unit recruited."));
278  return;
279  }
280  do_recruit(type->id(), side_num, recruit_hex);
281  }
282 }
283 
284 void menu_handler::repeat_recruit(int side_num, const map_location& last_hex)
285 {
286  const std::string& last_recruit = board().get_team(side_num).last_recruit();
287  if(last_recruit.empty() == false) {
288  map_location recruit_hex = last_hex;
289  do_recruit(last_recruit, side_num, recruit_hex);
290  }
291 }
292 
293 // TODO: Return multiple strings here, in case more than one error applies? For
294 // example, if you start AOI S5 with 0GP and recruit a Mage, two reasons apply,
295 // leader not on keep (extrarecruit=Mage) and not enough gold.
296 t_string menu_handler::can_recruit(const std::string& name, int side_num, map_location& loc, map_location& recruited_from)
297 {
298  team& current_team = board().get_team(side_num);
299 
300  const unit_type* u_type = unit_types.find(name);
301  if(u_type == nullptr) {
302  return _("Internal error. Please report this as a bug! Details:\n")
303  + "menu_handler::can_recruit: u_type == nullptr for " + name;
304  }
305 
306  // search for the unit to be recruited in recruits
307  if(!utils::contains(actions::get_recruits(side_num, loc), name)) {
308  return VGETTEXT("You cannot recruit a $unit_type_name at this time.",
309  utils::string_map { { "unit_type_name", u_type->type_name() }});
310  }
311 
312  // TODO take a wb::future_map RAII as unit_recruit::pre_show does
313  int wb_gold = 0;
314  {
315  wb::future_map future;
316  wb_gold = (pc_.get_whiteboard() ? pc_.get_whiteboard()->get_spent_gold_for(side_num) : 0);
317  }
318  if(u_type->cost() > current_team.gold() - wb_gold)
319  {
320  if(wb_gold > 0)
321  // TRANSLATORS: "plan" refers to Planning Mode
322  return _("At this point in your plan, you will not have enough gold to recruit this unit.");
323  else
324  return _("You do not have enough gold to recruit this unit.");
325  }
326 
327  current_team.last_recruit(name);
328  const events::command_disabler disable_commands;
329 
330  {
331  wb::future_map_if_active future; /* start planned unit map scope if in planning mode */
332  std::string msg = actions::find_recruit_location(side_num, loc, recruited_from, name);
333  if(!msg.empty()) {
334  return msg;
335  }
336  } // end planned unit map scope
337 
338  return "";
339 }
340 
341 bool menu_handler::do_recruit(const std::string& name, int side_num, map_location& loc)
342 {
343  map_location recruited_from = map_location::null_location();
344  const std::string res = can_recruit(name, side_num, loc, recruited_from);
345  team& current_team = board().get_team(side_num);
346 
347  if(res.empty() && (!pc_.get_whiteboard() || !pc_.get_whiteboard()->save_recruit(name, side_num, loc))) {
348  // MP_COUNTDOWN grant time bonus for recruiting
349  current_team.set_action_bonus_count(1 + current_team.action_bonus_count());
350 
351  // Do the recruiting.
352 
353  synced_context::run_and_throw("recruit", replay_helper::get_recruit(name, loc, recruited_from));
354  return true;
355  } else if(res.empty()) {
356  return false;
357  } else {
359  return false;
360  }
361 
362 }
363 
364 void menu_handler::recall(int side_num, const map_location& last_hex)
365 {
366  if(pc_.get_disallow_recall()) {
367  gui2::show_transient_message("", _("You are separated from your soldiers and may not recall them."));
368  return;
369  }
370 
371  team& current_team = board().get_team(side_num);
372 
373  std::vector<unit_const_ptr> recall_list_team;
374  bool empty;
375  {
376  wb::future_map future; // ensures recall list has planned recalls removed
377  recall_list_team = actions::get_recalls(side_num, last_hex);
378  empty = current_team.recall_list().empty();
379  }
380 
381  DBG_WB << "menu_handler::recall: Contents of wb-modified recall list:";
382  for(const unit_const_ptr& unit : recall_list_team) {
383  DBG_WB << unit->name() << " [" << unit->id() << "]";
384  }
385 
386  if(empty) {
388  _("There are no troops available to recall.\n(You must have veteran survivors from a previous scenario.)"));
389  return;
390  }
391  if(recall_list_team.empty()) {
392  gui2::show_transient_message("", _("You currently can't recall at the highlighted location."));
393  return;
394  }
395 
396  gui2::dialogs::unit_recall dlg(recall_list_team, current_team);
397 
398  if(!dlg.show()) {
399  return;
400  }
401 
402  int res = dlg.get_selected_index();
403  if (res < 0) {
404  gui2::show_transient_message("", _("No unit recalled."));
405  return;
406  }
407  int unit_cost = current_team.recall_cost();
408 
409  // we need to check if unit has a specific recall cost
410  // if it does we use it elsewise we use the team.recall_cost()
411  // the magic number -1 is what it gets set to if the unit doesn't
412  // have a special recall_cost of its own.
413  if(recall_list_team[res]->recall_cost() > -1) {
414  unit_cost = recall_list_team[res]->recall_cost();
415  }
416 
417  int wb_gold = pc_.get_whiteboard() ? pc_.get_whiteboard()->get_spent_gold_for(side_num) : 0;
418  if(current_team.gold() - wb_gold < unit_cost) {
419  utils::string_map i18n_symbols;
420  i18n_symbols["cost"] = std::to_string(unit_cost);
421  std::string msg = VNGETTEXT("You must have at least 1 gold piece to recall a unit.",
422  "You must have at least $cost gold pieces to recall this unit.", unit_cost, i18n_symbols);
424  return;
425  }
426 
427  LOG_NG << "recall index: " << res;
428  const events::command_disabler disable_commands;
429 
430  map_location recall_location = last_hex;
432  std::string err;
433  {
435  future; // future unit map removes invisible units from map, don't do this outside of planning mode
436  err = actions::find_recall_location(side_num, recall_location, recall_from, *recall_list_team[res].get());
437  } // end planned unit map scope
438 
439  if(!err.empty()) {
441  return;
442  }
443 
444  if(!pc_.get_whiteboard()
445  || !pc_.get_whiteboard()->save_recall(*recall_list_team[res].get(), side_num, recall_location)) {
446  bool success = synced_context::run_and_throw("recall",
447  replay_helper::get_recall(recall_list_team[res]->id(), recall_location, recall_from), true, true,
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, pc_.get_teams()[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  const display_context& dc = gui_->get_disp_context();
620  if(i != pc_.get_units().end() && i->is_visible_to_team(dc.get_team(gui_->viewing_side()), false)) {
621  gui_->scroll_to_tile(i->get_location(), game_display::WARP);
622  }
623 }
624 
625 void menu_handler::unit_description()
626 {
627  const unit_map::const_iterator un = current_unit();
628  if(un != pc_.get_units().end()) {
630  }
631 }
632 
633 void menu_handler::terrain_description(mouse_handler& mousehandler)
634 {
635  const map_location& loc = mousehandler.get_last_hex();
636  if(pc_.get_map().on_board(loc) == false || gui_->shrouded(loc)) {
637  return;
638  }
639 
640  const terrain_type& type = pc_.get_map().get_terrain_info(loc);
641  // const terrain_type& info = board().pc_.get_map().get_terrain_info(terrain);
643 }
644 
645 void menu_handler::rename_unit()
646 {
647  const unit_map::iterator un = current_unit();
648  if(un == pc_.get_units().end() || gui_->viewing_side() != un->side()) {
649  return;
650  }
651 
652  if(un->unrenamable()) {
653  return;
654  }
655 
656  std::string name = un->name();
657  const std::string title(_("Rename Unit"));
658  const std::string label(_("Name:"));
659 
660  if(gui2::dialogs::edit_text::execute(title, label, name)) {
661  resources::recorder->add_rename(name, un->get_location());
662  un->rename(name);
663  gui_->invalidate_unit();
664  }
665 }
666 
667 unit_map::iterator menu_handler::current_unit()
668 {
669  const mouse_handler& mousehandler = pc_.get_mouse_handler_base();
670  const bool see_all = gui_->show_everything() || (pc_.is_replay() && pc_.get_replay_controller()->see_all());
671 
672  unit_map::iterator res = board().find_visible_unit(mousehandler.get_last_hex(), pc_.get_teams()[gui_->viewing_team()], see_all);
673  if(res != pc_.get_units().end()) {
674  return res;
675  }
676 
677  return board().find_visible_unit(mousehandler.get_selected_hex(), pc_.get_teams()[gui_->viewing_team()], see_all);
678 }
679 
680 // Helpers for create_unit()
681 namespace
682 {
683 /** Allows a function to return both a type and a gender. */
684 typedef std::tuple<const unit_type*, unit_race::GENDER, std::string> type_gender_variation;
685 
686 /**
687  * Allows the user to select a type of unit, using GUI2.
688  * (Intended for use when a unit is created in debug mode via hotkey or
689  * context menu.)
690  * @returns the selected type and gender. If this is canceled, the
691  * returned type is nullptr.
692  */
693 type_gender_variation choose_unit()
694 {
695  //
696  // The unit creation dialog makes sure unit types
697  // are properly cached.
698  //
699  gui2::dialogs::unit_create create_dlg;
700  create_dlg.show();
701 
702  if(create_dlg.no_choice()) {
703  return type_gender_variation(nullptr, unit_race::NUM_GENDERS, "");
704  }
705 
706  const std::string& ut_id = create_dlg.choice();
707  const unit_type* utp = unit_types.find(ut_id);
708  if(!utp) {
709  ERR_NG << "Create unit dialog returned nonexistent or unusable unit_type id '" << ut_id << "'.";
710  return type_gender_variation(static_cast<const unit_type*>(nullptr), unit_race::NUM_GENDERS, "");
711  }
712  const unit_type& ut = *utp;
713 
714  unit_race::GENDER gender = create_dlg.gender();
715  // Do not try to set bad genders, may mess up l10n
716  // TODO: Is this actually necessary?
717  // (Maybe create_dlg can enforce proper gender selection?)
718  if(ut.genders().end() == std::find(ut.genders().begin(), ut.genders().end(), gender)) {
719  gender = ut.genders().front();
720  }
721 
722  return type_gender_variation(utp, gender, create_dlg.variation());
723 }
724 
725 /**
726  * Creates a unit and places it on the board.
727  * (Intended for use with any units created via debug mode.)
728  */
729 void create_and_place(game_display&,
730  const gamemap&,
731  unit_map&,
732  const map_location& loc,
733  const unit_type& u_type,
735  const std::string& variation = "")
736 {
737  synced_context::run_and_throw("debug_create_unit",
738  config {
739  "x", loc.wml_x(),
740  "y", loc.wml_y(),
741  "type", u_type.id(),
742  "gender", gender_string(gender),
743  "variation", variation,
744  }
745  );
746 }
747 
748 } // Anonymous namespace
749 
750 /**
751  * Creates a unit (in debug mode via hotkey or context menu).
752  */
753 void menu_handler::create_unit(mouse_handler& mousehandler)
754 {
755  // Save the current mouse location before popping up the choice menu (which
756  // gives time for the mouse to move, changing the location).
757  const map_location destination = mousehandler.get_last_hex();
758  assert(gui_ != nullptr);
759 
760  // Let the user select the kind of unit to create.
761  if(const auto& [type, gender, variation] = choose_unit(); type != nullptr) {
762  // Make it so.
763  create_and_place(*gui_, pc_.get_map(), pc_.get_units(), destination, *type, gender, variation);
764  }
765 }
766 
767 void menu_handler::change_side(mouse_handler& mousehandler)
768 {
769  const map_location& loc = mousehandler.get_last_hex();
770  const unit_map::iterator i = pc_.get_units().find(loc);
771  if(i == pc_.get_units().end()) {
772  if(!pc_.get_map().is_village(loc)) {
773  return;
774  }
775 
776  // village_owner returns 0 for free village, so team 0 will get it
777  int team = board().village_owner(loc);
778  // team is 0-based so team=team::nteams() is not a team
779  // but this will make get_village free it
780  if(team > static_cast<int>(pc_.get_teams().size())) {
781  team = 0;
782  }
784  } else {
785  int side = i->side();
786  ++side;
787  if(side > static_cast<int>(pc_.get_teams().size())) {
788  side = 1;
789  }
790  i->set_side(side);
791 
792  if(pc_.get_map().is_village(loc)) {
793  actions::get_village(loc, side);
794  }
795  }
796 }
797 
798 void menu_handler::kill_unit(mouse_handler& mousehandler)
799 {
800  const map_location loc = mousehandler.get_last_hex();
801  synced_context::run_and_throw("debug_kill", config {"x", loc.wml_x(), "y", loc.wml_y()});
802 }
803 
804 void menu_handler::label_terrain(mouse_handler& mousehandler, bool team_only)
805 {
806  const map_location& loc = mousehandler.get_last_hex();
807  if(pc_.get_map().on_board(loc) == false) {
808  return;
809  }
810 
811  const terrain_label* old_label = gui_->labels().get_label(loc);
812  std::string label = old_label ? old_label->text() : "";
813 
814  if(gui2::dialogs::edit_label::execute(label, team_only)) {
815  std::string team_name;
816  color_t color = font::LABEL_COLOR;
817 
818  if(team_only) {
819  team_name = gui_->labels().team_name();
820  } else {
821  color = team::get_side_color(gui_->viewing_side());
822  }
823  const terrain_label* res = gui_->labels().set_label(loc, label, gui_->viewing_team(), team_name, color);
824  if(res) {
826  }
827  }
828 }
829 
830 void menu_handler::clear_labels()
831 {
832  if(gui_->team_valid() && !board().is_observer()) {
833  const int res = gui2::show_message(
834  _("Clear Labels"),
835  _("Are you sure you want to clear map labels?"),
837  );
838 
839  if(res == gui2::retval::OK) {
840  gui_->labels().clear(gui_->current_team_name(), false);
841  resources::recorder->clear_labels(gui_->current_team_name(), 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, &pc_.get_undo_stack(), 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, &pc_.get_undo_stack());
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_ellipses(!prefs::get().ellipses());
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 config::attribute& a : alias_list->attribute_range()) {
1312  register_alias(a.second, a.first);
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  const std::time_t time = ::std::time(nullptr);
1328  std::stringstream ss;
1329  ss << time;
1330  cfg["time"] = ss.str();
1331 
1332  const int side = board().is_observer() ? 0 : gui_->viewing_side();
1333  if(!board().is_observer()) {
1334  cfg["side"] = side;
1335  }
1336 
1337  bool private_message = has_friends() && allies_only;
1338 
1339  if(private_message) {
1340  if(board().is_observer()) {
1341  cfg["to_sides"] = game_config::observer_team_name;
1342  } else {
1343  cfg["to_sides"] = pc_.get_teams()[gui_->viewing_team()].allied_human_teams();
1344  }
1345  }
1346 
1347  resources::recorder->speak(cfg);
1348 
1349  add_chat_message(time, cfg["id"], side, message,
1351 }
1352 
1353 void menu_handler::do_search(const std::string& new_search)
1354 {
1355  if(new_search.empty() == false && new_search != last_search_)
1356  last_search_ = new_search;
1357 
1358  if(last_search_.empty())
1359  return;
1360 
1361  bool found = false;
1362  map_location loc = last_search_hit_;
1363  // If this is a location search, just center on that location.
1364  std::vector<std::string> args = utils::split(last_search_, ',');
1365  if(args.size() == 2) {
1366  int x, y;
1367  x = lexical_cast_default<int>(args[0], 0) - 1;
1368  y = lexical_cast_default<int>(args[1], 0) - 1;
1369  if(x >= 0 && x < pc_.get_map().w() && y >= 0 && y < pc_.get_map().h()) {
1370  loc = map_location(x, y);
1371  found = true;
1372  }
1373  }
1374  // Start scanning the game map
1375  if(loc.valid() == false) {
1376  loc = map_location(pc_.get_map().w() - 1, pc_.get_map().h() - 1);
1377  }
1378 
1379  map_location start = loc;
1380  while(!found) {
1381  // Move to the next location
1382  loc.x = (loc.x + 1) % pc_.get_map().w();
1383  if(loc.x == 0)
1384  loc.y = (loc.y + 1) % pc_.get_map().h();
1385 
1386  // Search label
1387  if(!gui_->shrouded(loc)) {
1388  const terrain_label* label = gui_->labels().get_label(loc);
1389  if(label) {
1390  std::string label_text = label->text().str();
1391  if(std::search(label_text.begin(), label_text.end(), last_search_.begin(), last_search_.end(),
1393  != label_text.end()) {
1394  found = true;
1395  }
1396  }
1397  }
1398  // Search unit name
1399  if(!gui_->fogged(loc)) {
1400  unit_map::const_iterator ui = pc_.get_units().find(loc);
1401  if(ui != pc_.get_units().end()) {
1402  const std::string name = ui->name();
1403  if(std::search(
1404  name.begin(), name.end(), last_search_.begin(), last_search_.end(), utils::chars_equal_insensitive)
1405  != name.end()) {
1406  if(!pc_.get_teams()[gui_->viewing_team()].is_enemy(ui->side())
1407  || !ui->invisible(ui->get_location())) {
1408  found = true;
1409  }
1410  }
1411  }
1412  }
1413 
1414  if(loc == start)
1415  break;
1416  }
1417 
1418  if(found) {
1419  last_search_hit_ = loc;
1420  gui_->scroll_to_tile(loc, game_display::ONSCREEN, false);
1421  gui_->highlight_hex(loc);
1422  } else {
1423  last_search_hit_ = map_location();
1424  // Not found, inform the player
1425  utils::string_map symbols;
1426  symbols["search"] = last_search_;
1427  const std::string msg = VGETTEXT("Could not find label or unit "
1428  "containing the string ‘$search’.",
1429  symbols);
1431  }
1432 }
1433 
1434 void menu_handler::do_command(const std::string& str)
1435 {
1436  console_handler ch(*this);
1437  ch.dispatch(str);
1438 }
1439 
1440 std::vector<std::string> menu_handler::get_commands_list()
1441 {
1442  console_handler ch(*this);
1443  // HACK: we need to call dispatch() at least once to get the
1444  // command list populated *at all*. Terrible design.
1445  // An empty command is silently ignored and has negligible
1446  // overhead, so we use that for this purpose here.
1447  ch.dispatch("");
1448  return ch.get_commands_list();
1449 }
1450 
1451 void console_handler::do_refresh()
1452 {
1455 
1456  menu_handler_.gui_->create_buttons();
1457  menu_handler_.gui_->queue_rerender();
1458 }
1459 
1460 void console_handler::do_droid()
1461 {
1462  // :droid [<side> [on/off/full]]
1463  const std::string side_s = get_arg(1);
1464  std::string action = get_arg(2);
1465  std::transform(action.begin(), action.end(), action.begin(), tolower);
1466  // default to the current side if empty
1467  const unsigned int side = side_s.empty() ? team_num_ : lexical_cast_default<unsigned int>(side_s);
1468  const bool is_your_turn = menu_handler_.pc_.current_side() == static_cast<int>(menu_handler_.gui_->viewing_side());
1469 
1470  utils::string_map symbols;
1471  symbols["side"] = std::to_string(side);
1472 
1473  if(side < 1 || side > menu_handler_.pc_.get_teams().size()) {
1474  command_failed(VGETTEXT("Can't droid invalid side: '$side'.", symbols));
1475  return;
1476  } else if(menu_handler_.board().get_team(side).is_network()) {
1477  command_failed(VGETTEXT("Can't droid networked side: '$side'.", symbols));
1478  return;
1479  } else if(menu_handler_.board().get_team(side).is_local()) {
1480  bool changed = false;
1481 
1482  const bool is_human = menu_handler_.board().get_team(side).is_human();
1483  const bool is_droid = menu_handler_.board().get_team(side).is_droid();
1484  const bool is_proxy_human = menu_handler_.board().get_team(side).is_proxy_human();
1485  const bool is_ai = menu_handler_.board().get_team(side).is_ai();
1486 
1487  if(action == "on") {
1488  if(is_ai && !is_your_turn) {
1489  command_failed(_("It is not allowed to change a side from AI to human control when it's not your turn."));
1490  return;
1491  }
1492  if(!is_human || !is_droid) {
1493  menu_handler_.board().get_team(side).make_human();
1494  menu_handler_.board().get_team(side).make_droid();
1495  changed = true;
1496  if(is_ai) {
1497  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1498  }
1499  print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: AI.", symbols));
1500  } else {
1501  print(get_cmd(), VGETTEXT("Side '$side' is already droided.", symbols));
1502  }
1503  } else if(action == "off") {
1504  if(is_ai && !is_your_turn) {
1505  command_failed(_("It is not allowed to change a side from AI to human control when it's not your turn."));
1506  return;
1507  }
1508  if(!is_human || !is_proxy_human) {
1509  menu_handler_.board().get_team(side).make_human();
1510  menu_handler_.board().get_team(side).make_proxy_human();
1511  changed = true;
1512  if(is_ai) {
1513  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1514  }
1515  print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: human.", symbols));
1516  } else {
1517  print(get_cmd(), VGETTEXT("Side '$side' is already not droided.", symbols));
1518  }
1519  } else if(action == "full") {
1520  if(!is_your_turn) {
1521  command_failed(_("It is not allowed to change a side from human to AI control when it's not your turn."));
1522  return;
1523  }
1524  if(!is_ai || !is_droid) {
1525  menu_handler_.board().get_team(side).make_ai();
1526  menu_handler_.board().get_team(side).make_droid();
1527  changed = true;
1528  if(is_human || is_proxy_human) {
1529  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::ai}});
1530  }
1531  print(get_cmd(), VGETTEXT("Side '$side' controller is now fully controlled by: AI.", symbols));
1532  } else {
1533  print(get_cmd(), VGETTEXT("Side '$side' is already fully AI controlled.", symbols));
1534  }
1535  } else if(action == "") {
1536  if(is_ai && !is_your_turn) {
1537  command_failed(_("It is not allowed to change a side from AI to human control when it's not your turn."));
1538  return;
1539  }
1540  if(is_ai || is_droid) {
1541  menu_handler_.board().get_team(side).make_human();
1542  menu_handler_.board().get_team(side).make_proxy_human();
1543  changed = true;
1544  if(is_ai) {
1545  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1546  }
1547  print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: human.", symbols));
1548  } else {
1549  menu_handler_.board().get_team(side).make_human();
1550  menu_handler_.board().get_team(side).make_droid();
1551  changed = true;
1552  if(is_ai) {
1553  menu_handler_.pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", prefs::get().login(), "to", side_controller::human}});
1554  }
1555  print(get_cmd(), VGETTEXT("Side '$side' controller is now controlled by: AI.", symbols));
1556  }
1557  } else {
1558  print(get_cmd(), VGETTEXT("Invalid action provided for side '$side'. Valid actions are: on, off, full.", symbols));
1559  }
1560 
1561  if(team_num_ == side && changed) {
1562  if(playsingle_controller* psc = dynamic_cast<playsingle_controller*>(&menu_handler_.pc_)) {
1563  psc->set_player_type_changed();
1564  }
1565  }
1566  }
1567  menu_handler_.textbox_info_.close();
1568 }
1569 
1570 void console_handler::do_terrain()
1571 {
1572  // :terrain [<terrain type> [both|base|overlay]]
1573  const std::string terrain_type = get_arg(1);
1574  const std::string mode_str = get_arg(2);
1575 
1576  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
1577  const map_location& loc = mousehandler.get_last_hex();
1578 
1579  synced_context::run_and_throw("debug_terrain",
1580  config {
1581  "x", loc.wml_x(),
1582  "y", loc.wml_y(),
1583  "terrain_type", terrain_type,
1584  "mode_str", mode_str,
1585  }
1586  );
1587 }
1588 
1589 void console_handler::do_idle()
1590 {
1591  // :idle [<side> [on/off]]
1592  const std::string side_s = get_arg(1);
1593  const std::string action = get_arg(2);
1594  // default to the current side if empty
1595  const unsigned int side = side_s.empty() ? team_num_ : lexical_cast_default<unsigned int>(side_s);
1596 
1597  if(side < 1 || side > menu_handler_.pc_.get_teams().size()) {
1598  utils::string_map symbols;
1599  symbols["side"] = side_s;
1600  command_failed(VGETTEXT("Can't idle invalid side: '$side'.", symbols));
1601  return;
1602  } else if(menu_handler_.board().get_team(side).is_network()) {
1603  utils::string_map symbols;
1604  symbols["side"] = std::to_string(side);
1605  command_failed(VGETTEXT("Can't idle networked side: '$side'.", symbols));
1606  return;
1607  } else if(menu_handler_.board().get_team(side).is_local_ai()) {
1608  utils::string_map symbols;
1609  symbols["side"] = std::to_string(side);
1610  command_failed(VGETTEXT("Can't idle local ai side: '$side'.", symbols));
1611  return;
1612  } else if(menu_handler_.board().get_team(side).is_local_human()) {
1613  if(menu_handler_.board().get_team(side).is_idle() ? action == " on" : action == " off") {
1614  return;
1615  }
1616  // toggle the proxy controller between idle / non idle
1617  menu_handler_.board().get_team(side).toggle_idle();
1618  if(team_num_ == side) {
1619  if(playsingle_controller* psc = dynamic_cast<playsingle_controller*>(&menu_handler_.pc_)) {
1620  psc->set_player_type_changed();
1621  }
1622  }
1623  }
1624  menu_handler_.textbox_info_.close();
1625 }
1626 
1627 void console_handler::do_theme()
1628 {
1630 }
1631 
1633 {
1634  save_id_matches(const std::string& save_id)
1635  : save_id_(save_id)
1636  {
1637  }
1638 
1639  bool operator()(const team& t) const
1640  {
1641  return t.save_id() == save_id_;
1642  }
1643 
1644  std::string save_id_;
1645 };
1646 
1647 void console_handler::do_control()
1648 {
1649  // :control <side> <nick>
1650  if(!menu_handler_.pc_.is_networked_mp()) {
1651  return;
1652  }
1653 
1654  const std::string side = get_arg(1);
1655  const std::string player = get_arg(2);
1656  if(player.empty()) {
1657  command_failed_need_arg(2);
1658  return;
1659  }
1660 
1661  unsigned int side_num;
1662  try {
1663  side_num = lexical_cast<unsigned int>(side);
1664  } catch(const bad_lexical_cast&) {
1665  const auto& teams = menu_handler_.pc_.get_teams();
1666  const auto it_t = std::find_if(teams.begin(), teams.end(), save_id_matches(side));
1667 
1668  if(it_t == teams.end()) {
1669  utils::string_map symbols;
1670  symbols["side"] = side;
1671  command_failed(VGETTEXT("Can't change control of invalid side: '$side'.", symbols));
1672  return;
1673  } else {
1674  side_num = it_t->side();
1675  }
1676  }
1677 
1678  if(side_num < 1 || side_num > menu_handler_.pc_.get_teams().size()) {
1679  utils::string_map symbols;
1680  symbols["side"] = side;
1681  command_failed(VGETTEXT("Can't change control of out-of-bounds side: '$side'.", symbols));
1682  return;
1683  }
1684 
1685  menu_handler_.request_control_change(side_num, player);
1686  menu_handler_.textbox_info_.close();
1687 }
1688 
1689 void console_handler::do_controller()
1690 {
1691  const std::string side = get_arg(1);
1692  unsigned int side_num;
1693  try {
1694  side_num = lexical_cast<unsigned int>(side);
1695  } catch(const bad_lexical_cast&) {
1696  utils::string_map symbols;
1697  symbols["side"] = side;
1698  command_failed(VGETTEXT("Can't query control of invalid side: '$side'.", symbols));
1699  return;
1700  }
1701 
1702  if(side_num < 1 || side_num > menu_handler_.pc_.get_teams().size()) {
1703  utils::string_map symbols;
1704  symbols["side"] = side;
1705  command_failed(VGETTEXT("Can't query control of out-of-bounds side: '$side'.", symbols));
1706  return;
1707  }
1708 
1709  std::string report = side_controller::get_string(menu_handler_.board().get_team(side_num).controller());
1710  if(!menu_handler_.board().get_team(side_num).is_proxy_human()) {
1711  report += " (" + side_proxy_controller::get_string(menu_handler_.board().get_team(side_num).proxy_controller()) + ")";
1712  }
1713 
1714  if(menu_handler_.board().get_team(side_num).is_network()) {
1715  report += " (networked)";
1716  }
1717 
1718  print(get_cmd(), report);
1719 }
1720 
1721 void console_handler::do_clear()
1722 {
1723  menu_handler_.gui_->get_chat_manager().clear_chat_messages();
1724 }
1725 
1726 void console_handler::do_foreground()
1727 {
1728  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_FOREGROUND);
1729  menu_handler_.gui_->invalidate_all();
1730 }
1731 
1732 void console_handler::do_layers()
1733 {
1734  display& disp = *(menu_handler_.gui_);
1735 
1736  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
1737  const map_location& loc = mousehandler.get_last_hex();
1738 
1739  //
1740  // It's possible to invoke this dialog even if loc isn't a valid hex. I'm not sure
1741  // exactly how that happens, but it does seem to occur when moving the mouse outside
1742  // the window to the menu bar. Not sure if there's a bug when the set-last-hex code
1743  // in that case, but this check at least ensures the dialog is only ever shown for a
1744  // valid, on-map location. Otherwise, an assertion gets thrown.
1745  //
1746  // -- vultraz, 2017-09-21
1747  //
1748  if(disp.get_map().on_board_with_border(loc)) {
1749  gui2::dialogs::terrain_layers::display(disp, loc);
1750  }
1751 }
1752 
1753 void console_handler::do_fps()
1754 {
1755  prefs::get().set_show_fps(!prefs::get().show_fps());
1756 }
1757 
1758 void console_handler::do_benchmark()
1759 {
1760  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_BENCHMARK);
1761 }
1762 
1763 void console_handler::do_save()
1764 {
1765  menu_handler_.pc_.do_consolesave(get_data());
1766 }
1767 
1768 void console_handler::do_save_quit()
1769 {
1770  do_save();
1771  do_quit();
1772 }
1773 
1774 void console_handler::do_quit()
1775 {
1777 }
1778 
1779 void console_handler::do_ignore_replay_errors()
1780 {
1781  game_config::ignore_replay_errors = (get_data() != "off") ? true : false;
1782 }
1783 
1784 void console_handler::do_nosaves()
1785 {
1786  game_config::disable_autosave = (get_data() != "off") ? true : false;
1787 }
1788 
1789 void console_handler::do_next_level()
1790 {
1791  synced_context::run_and_throw("debug_next_level", config {"next_level", get_data()});
1792 }
1793 
1794 void console_handler::do_choose_level()
1795 {
1796  std::string tag = menu_handler_.pc_.get_classification().get_tagname();
1797  std::vector<std::string> options;
1798  std::string next;
1799  if(tag != "multiplayer") {
1800  for(const config& sc : menu_handler_.game_config_.child_range(tag)) {
1801  const std::string& id = sc["id"];
1802  options.push_back(id);
1803  if(id == menu_handler_.gamedata().next_scenario()) {
1804  next = id;
1805  }
1806  }
1807  } else {
1808  // find scenarios of multiplayer campaigns
1809  // (assumes that scenarios are ordered properly in the game_config)
1810  std::string scenario_id = menu_handler_.pc_.get_mp_settings().mp_scenario;
1811  if(auto this_scenario = menu_handler_.game_config_.find_child(tag, "id", scenario_id)) {
1812  std::string addon_id = this_scenario["addon_id"].str();
1813  for(const config& sc : menu_handler_.game_config_.child_range(tag)) {
1814  if(sc["addon_id"] == addon_id) {
1815  std::string id = sc["id"];
1816  options.push_back(id);
1817  if(id == menu_handler_.gamedata().next_scenario()) {
1818  next = id;
1819  }
1820  }
1821  }
1822  }
1823  }
1824  std::sort(options.begin(), options.end());
1825  int choice = std::distance(options.begin(), std::lower_bound(options.begin(), options.end(), next));
1826  {
1827  gui2::dialogs::simple_item_selector dlg(_("Choose Scenario (Debug!)"), "", options);
1828  dlg.set_selected_index(choice);
1829  dlg.show();
1830  choice = dlg.selected_index();
1831  }
1832 
1833  if(choice == -1) {
1834  return;
1835  }
1836 
1837  if(std::size_t(choice) < options.size()) {
1838  synced_context::run_and_throw("debug_next_level", config {"next_level", options[choice]});
1839  }
1840 }
1841 
1842 void console_handler::do_turn()
1843 {
1844  tod_manager& tod_man = menu_handler_.gamestate().tod_manager_;
1845 
1846  int turn = tod_man.turn() + 1;
1847  const std::string& data = get_data();
1848  if(!data.empty()) {
1849  turn = lexical_cast_default<int>(data, 1);
1850  }
1851  synced_context::run_and_throw("debug_turn", config {"turn", turn});
1852 }
1853 
1854 void console_handler::do_turn_limit()
1855 {
1856  int limit = get_data().empty() ? -1 : lexical_cast_default<int>(get_data(), 1);
1857  synced_context::run_and_throw("debug_turn_limit", config {"turn_limit", limit});
1858 }
1859 
1860 void console_handler::do_debug()
1861 {
1862  if(!menu_handler_.pc_.is_networked_mp() || game_config::mp_debug) {
1863  print(get_cmd(), _("Debug mode activated!"));
1864  game_config::set_debug(true);
1865  } else {
1866  command_failed(_("Debug mode not available in network games"));
1867  }
1868 }
1869 
1870 void console_handler::do_nodebug()
1871 {
1872  if(game_config::debug) {
1873  print(get_cmd(), _("Debug mode deactivated!"));
1874  game_config::set_debug(false);
1875  }
1876 }
1877 
1878 void console_handler::do_lua()
1879 {
1880  if(!menu_handler_.gamestate().lua_kernel_) {
1881  return;
1882  }
1883 
1884  synced_context::run_and_throw("debug_lua", config {"code", get_data()});
1885 }
1886 
1887 void console_handler::do_unsafe_lua()
1888 {
1889  if(!menu_handler_.gamestate().lua_kernel_) {
1890  return;
1891  }
1892 
1893  const int retval = gui2::show_message(_("WARNING! Unsafe Lua Mode"),
1894  _("Executing Lua code in in this manner opens your computer to potential security breaches from any "
1895  "malicious add-ons or other programs you may have installed.\n\n"
1896  "Do not continue unless you really know what you are doing."), gui2::dialogs::message::ok_cancel_buttons);
1897 
1898  if(retval == gui2::retval::OK) {
1899  print(get_cmd(), _("Unsafe mode enabled!"));
1900  menu_handler_.gamestate().lua_kernel_->load_package();
1901  }
1902 }
1903 
1904 void console_handler::do_custom()
1905 {
1906  prefs::get().set_custom_command(get_data());
1907 }
1908 
1909 void console_handler::do_set_alias()
1910 {
1911  const std::string data = get_data();
1912  const std::string::const_iterator j = std::find(data.begin(), data.end(), '=');
1913  const std::string alias(data.begin(), j);
1914  if(j != data.end()) {
1915  const std::string command(j + 1, data.end());
1916  if(!command.empty()) {
1917  register_alias(command, alias);
1918  } else {
1919  // "alias something=" deactivate this alias. We just set it
1920  // equal to itself here. Later preferences will filter empty alias.
1921  register_alias(alias, alias);
1922  }
1923  prefs::get().add_alias(alias, command);
1924  // directly save it for the moment, but will slow commands sequence
1926  } else {
1927  // "alias something" display its value
1928  // if no alias, will be "'something' = 'something'"
1929  const std::string command = chmap::get_actual_cmd(alias);
1930  print(get_cmd(), "'" + alias + "'" + " = " + "'" + command + "'");
1931  }
1932 }
1933 
1934 void console_handler::do_set_var()
1935 {
1936  const std::string data = get_data();
1937  if(data.empty()) {
1938  command_failed_need_arg(1);
1939  return;
1940  }
1941 
1942  const std::string::const_iterator j = std::find(data.begin(), data.end(), '=');
1943  if(j != data.end()) {
1944  const std::string name(data.begin(), j);
1945  const std::string value(j + 1, data.end());
1946  synced_context::run_and_throw("debug_set_var", config {"name", name, "value", value});
1947  } else {
1948  command_failed(_("Variable not found"));
1949  }
1950 }
1951 
1952 void console_handler::do_show_var()
1953 {
1954  gui2::show_transient_message("", menu_handler_.gamedata().get_variable_const(get_data()));
1955 }
1956 
1957 void console_handler::do_inspect()
1958 {
1960  gui2::dialogs::gamestate_inspector::display(
1961  menu_handler_.gamedata().get_variables(), *resources::game_events, menu_handler_.board());
1962 }
1963 
1964 void console_handler::do_control_dialog()
1965 {
1966  gui2::dialogs::mp_change_control::display(menu_handler_);
1967 }
1968 
1969 void console_handler::do_unit()
1970 {
1971  // prevent SIGSEGV due to attempt to set HP during a fight
1972  if(events::commands_disabled > 0) {
1973  return;
1974  }
1975 
1976  unit_map::iterator i = menu_handler_.current_unit();
1977  if(i == menu_handler_.pc_.get_units().end()) {
1978  utils::string_map symbols;
1979  symbols["unit"] = get_arg(1);
1980  command_failed(VGETTEXT(
1981  "Debug command 'unit: $unit' failed: no unit selected or hovered over.",
1982  symbols));
1983  return;
1984  }
1985 
1986  const map_location loc = i->get_location();
1987  const std::string data = get_data(1);
1988  std::vector<std::string> parameters = utils::split(data, '=', utils::STRIP_SPACES);
1989  if(parameters.size() < 2) {
1990  return;
1991  }
1992 
1993  if(parameters[0] == "alignment") {
1994  auto alignment = unit_alignments::get_enum(parameters[1]);
1995  if(!alignment) {
1996  utils::string_map symbols;
1997  symbols["alignment"] = get_arg(1);
1998  command_failed(VGETTEXT(
1999  "Invalid alignment: '$alignment', needs to be one of lawful, neutral, chaotic, or liminal.",
2000  symbols));
2001  return;
2002  }
2003  }
2004 
2005  synced_context::run_and_throw("debug_unit",
2006  config {
2007  "x", loc.wml_x(),
2008  "y", loc.wml_y(),
2009  "name", parameters[0],
2010  "value", parameters[1],
2011  }
2012  );
2013 }
2014 
2015 void console_handler::do_discover()
2016 {
2017  for(const unit_type_data::unit_type_map::value_type& i : unit_types.types()) {
2018  prefs::get().encountered_units().insert(i.second.id());
2019  }
2020 }
2021 
2022 void console_handler::do_undiscover()
2023 {
2024  const int res = gui2::show_message("Undiscover",
2025  _("Do you wish to clear all of your discovered units from help?"), gui2::dialogs::message::yes_no_buttons);
2026  if(res != gui2::retval::CANCEL) {
2027  prefs::get().encountered_units().clear();
2028  }
2029 }
2030 
2031 /** Implements the (debug mode) console command that creates a unit. */
2032 void console_handler::do_create()
2033 {
2034  const mouse_handler& mousehandler = menu_handler_.pc_.get_mouse_handler_base();
2035  const map_location& loc = mousehandler.get_last_hex();
2036  if(menu_handler_.pc_.get_map().on_board(loc)) {
2037  const unit_type* ut = unit_types.find(get_data());
2038  if(!ut) {
2039  command_failed(_("Invalid unit type"));
2040  return;
2041  }
2042 
2043  // Create the unit.
2044  create_and_place(*menu_handler_.gui_, menu_handler_.pc_.get_map(), menu_handler_.pc_.get_units(), loc, *ut);
2045  } else {
2046  command_failed(_("Invalid location"));
2047  }
2048 }
2049 
2050 void console_handler::do_fog()
2051 {
2052  synced_context::run_and_throw("debug_fog", config());
2053 }
2054 
2055 void console_handler::do_shroud()
2056 {
2057  synced_context::run_and_throw("debug_shroud", config());
2058 }
2059 
2060 void console_handler::do_gold()
2061 {
2062  synced_context::run_and_throw("debug_gold", config {"gold", lexical_cast_default<int>(get_data(), 1000)});
2063 }
2064 
2065 void console_handler::do_event()
2066 {
2067  synced_context::run_and_throw("debug_event", config {"eventname", get_data()});
2068 }
2069 
2070 void console_handler::do_toggle_draw_coordinates()
2071 {
2072  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_COORDINATES);
2073  menu_handler_.gui_->invalidate_all();
2074 }
2075 void console_handler::do_toggle_draw_terrain_codes()
2076 {
2077  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_TERRAIN_CODES);
2078  menu_handler_.gui_->invalidate_all();
2079 }
2080 
2081 void console_handler::do_toggle_draw_num_of_bitmaps()
2082 {
2083  menu_handler_.gui_->toggle_debug_flag(display::DEBUG_NUM_BITMAPS);
2084  menu_handler_.gui_->invalidate_all();
2085 }
2086 
2087 void console_handler::do_toggle_whiteboard()
2088 {
2089  if(const std::shared_ptr<wb::manager>& whiteb = menu_handler_.pc_.get_whiteboard()) {
2090  whiteb->set_active(!whiteb->is_active());
2091  if(whiteb->is_active()) {
2092  print(get_cmd(), _("Planning mode activated!"));
2093  whiteb->print_help_once();
2094  } else {
2095  print(get_cmd(), _("Planning mode deactivated!"));
2096  }
2097  }
2098 }
2099 
2100 void console_handler::do_whiteboard_options()
2101 {
2102  if(menu_handler_.pc_.get_whiteboard()) {
2103  menu_handler_.pc_.get_whiteboard()->options_dlg();
2104  }
2105 }
2106 
2107 void menu_handler::do_ai_formula(const std::string& str, int side_num, mouse_handler& /*mousehandler*/)
2108 {
2109  try {
2110  add_chat_message(std::time(nullptr), "wfl", 0, ai::manager::get_singleton().evaluate_command(side_num, str));
2111  } catch(const wfl::formula_error&) {
2112  } catch(...) {
2113  add_chat_message(std::time(nullptr), "wfl", 0, "UNKNOWN ERROR IN FORMULA: "+utils::get_unknown_exception_type());
2114  }
2115 }
2116 
2117 void menu_handler::user_command()
2118 {
2119  textbox_info_.show(gui::TEXTBOX_COMMAND, translation::sgettext("prompt^Command:"), "", false, *gui_);
2120 }
2121 
2122 void menu_handler::request_control_change(int side_num, const std::string& player)
2123 {
2124  std::string side = std::to_string(side_num);
2125  if(board().get_team(side_num).is_local_human() && player == prefs::get().login()) {
2126  // this is already our side.
2127  return;
2128  } else {
2129  // The server will (or won't because we aren't allowed to change the controller)
2130  // send us a [change_controller] back, which we then handle in playturn.cpp
2131  pc_.send_to_wesnothd(config {"change_controller", config {"side", side, "player", player}});
2132  }
2133 }
2134 
2135 void menu_handler::custom_command()
2136 {
2137  for(const std::string& command : utils::split(prefs::get().custom_command(), ';')) {
2138  do_command(command);
2139  }
2140 }
2141 
2142 void menu_handler::ai_formula()
2143 {
2144  if(!pc_.is_networked_mp()) {
2145  textbox_info_.show(gui::TEXTBOX_AI, translation::sgettext("prompt^Command:"), "", false, *gui_);
2146  }
2147 }
2148 
2149 void menu_handler::clear_messages()
2150 {
2151  gui_->get_chat_manager().clear_chat_messages(); // also clear debug-messages and WML-error-messages
2152 }
2153 
2155 {
2156  pc_.send_to_wesnothd(cfg);
2157 }
2158 
2159 } // end namespace events
Various functions related to moving units.
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
double t
Definition: astarsearch.cpp:63
static manager & get_singleton()
Definition: manager.hpp:142
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
child_itors child_range(config_key_type key)
Definition: config.cpp:273
attribute_map::value_type attribute
Definition: config.hpp:299
Abstract class for exposing game data that doesn't depend on the GUI, however which for historical re...
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
bool is_observer() const
Check if we are an observer in this game.
can_move_result unit_can_move(const unit &u) const
Work out what u can do - this does not check which player's turn is currently active,...
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:123
@ ONSCREEN
Definition: display.hpp:506
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition: display.hpp:978
@ DEBUG_BENCHMARK
Toggle to continuously redraw the whole map.
Definition: display.hpp:990
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition: display.hpp:984
@ DEBUG_FOREGROUND
Separates background and foreground terrain layers.
Definition: display.hpp:987
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition: display.hpp:981
const gamemap & get_map() const
Definition: display.hpp:106
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition: display.cpp:2324
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:87
game_data & gamedata()
gui::floating_textbox textbox_info_
void show_statistics(int side_num)
void recruit(int side_num, const map_location &last_hex)
play_controller & pc_
t_string can_recruit(const std::string &name, int side_num, map_location &target_hex, map_location &recruited_from)
game_display * gui_
bool has_friends() const
game_state & gamestate() const
void disable_units_highlight()
Use this to disable hovering an unit from highlighting its movement range.
void set_current_paths(const pathfind::paths &new_paths)
play_controller & pc_
map_location get_selected_hex() const
const map_location hovered_hex() const
Uses SDL and game_display::hex_clicked_on to fetch the hex the mouse is hovering, if applicable.
const map_location & get_last_hex() const
bool hex_hosts_unit(const map_location &hex) const
Unit exists on the hex, no matter if friend or foe.
void cycle_units(const bool browse, const bool reverse=false)
pathfind::marked_route get_route(const unit *un, map_location go_to, team &team) const
Game board class.
Definition: game_board.hpp: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
bool on_board_with_border(const map_location &loc) const
Definition: map.cpp:389
Encapsulates the map of the game.
Definition: map.hpp:172
std::string write() const
Definition: map.cpp:209
file_dialog & set_extension(const std::string &value)
Sets 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 int viewing_team, int &selected_side_number)
Definition: game_stats.hpp:35
@ yes_no_buttons
Shows a yes and no button.
Definition: message.hpp:81
@ ok_cancel_buttons
Shows an ok and cancel button.
Definition: message.hpp:77
@ auto_close
Enables auto close.
Definition: message.hpp:71
bool show(const unsigned auto_close_time=0)
Shows the window.
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()
void set_custom_command(const std::string &command)
void set_grid(bool ison)
void set_ellipses(bool ison)
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:294
void add_label(const terrain_label *)
Definition: replay.cpp:273
void speak(const config &cfg)
Definition: replay.cpp:351
void clear_labels(const std::string &, bool)
Definition: replay.cpp:284
static void ignore_error_function(const std::string &message)
A function to be passed to run_in_synced_context to ignore the error.
static bool run_and_throw(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
static synced_state get_synced_state()
bool empty() const
Definition: tstring.hpp:186
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
void set_action_bonus_count(const int count)
Definition: team.hpp:200
int recall_cost() const
Definition: team.hpp:179
bool auto_shroud_updates() const
Definition: team.hpp:324
int action_bonus_count() const
Definition: team.hpp:199
int gold() const
Definition: team.hpp:175
static color_t get_side_color(int side)
Definition: team.cpp:959
recall_list_manager & recall_list()
Definition: team.hpp:201
const std::string & last_recruit() const
Definition: team.hpp:214
To store label data Class implements logic for rendering.
Definition: label.hpp:111
const t_string & text() const
Definition: label.hpp:139
int turn() const
Container associating units to locations.
Definition: map.hpp:98
@ NUM_GENDERS
Definition: race.hpp: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:1267
const unit_type_map & types() const
Definition: types.hpp:395
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:968
int w
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
void throw_quit_game_exception()
int dispatch(lua_State *L)
static std::string _(const char *str)
Definition: gettext.hpp:93
bool user_end_turn() const
Check whether the user ended their turn.
Definition: unit.hpp:792
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:380
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:403
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1398
bool has_moved() const
Checks if this unit has moved.
Definition: unit.hpp:1353
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1435
int movement_left() const
Gets how far a unit can move, considering the incapacitated flag.
Definition: unit.hpp:1323
const map_location & get_goto() const
The map location to which this unit is moving over multiple turns, if any.
Definition: unit.hpp:1429
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:207
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:205
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
Definition: menu_events.cpp:82
#define LOG_NG
Definition: menu_events.cpp:83
std::string find_recruit_location(const int side, map_location &recruit_location, map_location &recruited_from, const std::string &unit_type)
Finds a location on which to place a unit.
Definition: create.cpp:466
const std::set< std::string > get_recruits(int side, const map_location &recruit_loc)
Gets the recruitable units from a side's leaders' personal recruit lists who can recruit on or from a...
Definition: create.cpp:60
std::size_t move_unit_and_record(const std::vector< map_location > &steps, undo_list *undo_stack, bool continued_move, bool show_move, bool *interrupted, move_unit_spectator *move_spectator)
Moves a unit across the board.
Definition: move.cpp:1348
game_events::pump_result_t get_village(const map_location &loc, int side, bool *action_timebonus, bool fire_event)
Makes it so the village at the given location is owned by the given side.
Definition: move.cpp:139
std::vector< unit_const_ptr > get_recalls(int side, const map_location &recall_loc)
Gets the recallable units for a side, restricted by that side's leaders' personal abilities to recall...
Definition: create.cpp:160
std::string find_recall_location(const int side, map_location &recall_location, map_location &recall_from, const unit &unit_recall)
Finds a location on which to recall unit_recall.
Definition: create.cpp:330
CURSOR_TYPE get()
Definition: cursor.cpp:216
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
Handling of system events.
static bfs::path get_dir(const bfs::path &dirpath)
Definition: filesystem.cpp:335
std::string get_user_data_dir()
Definition: filesystem.cpp:793
const std::string map_extension
Definition: filesystem.hpp:78
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:89
bool ignore_replay_errors
Definition: game_config.cpp:85
const bool & debug
Definition: game_config.cpp:92
bool disable_autosave
Definition: game_config.cpp:89
const std::string observer_team_name
observer team name used for observer team chat
Definition: filesystem.cpp:99
void set_debug(bool new_debug)
Definition: game_config.cpp:94
void show_unit_list(display &gui)
Definition: unit_list.cpp:193
void show_transient_error_message(const std::string &message, const std::string &image, const bool message_use_markup)
Shows a transient error message to the user.
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:150
retval
Default window/dialog return values.
Definition: retval.hpp:30
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
General purpose widgets.
@ TEXTBOX_MESSAGE
@ TEXTBOX_SEARCH
@ TEXTBOX_COMMAND
Definition: help.cpp:53
void show_help(const std::string &show_topic, int xloc, int yloc)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:140
void show_terrain_description(const terrain_type &t)
Definition: help.cpp:77
void show_unit_description(const unit &u)
Definition: help.cpp:71
void flush_cache()
Purges all image caches.
Definition: picture.cpp:216
logger & err()
Definition: log.cpp:304
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:193
static std::string sgettext(const char *str)
Definition: gettext.hpp:62
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
@ STRIP_SPACES
REMOVE_EMPTY: remove empty elements.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:83
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
bool chars_equal_insensitive(char a, char b)
Definition: general.hpp:23
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:194
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
const std::string & gender_string(unit_race::GENDER gender)
Definition: race.cpp:140
Replay control code.
Thrown when a lexical_cast fails.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
save_id_matches(const std::string &save_id)
bool operator()(const team &t) const
An exception object used when an IO error occurs.
Definition: filesystem.hpp:66
Encapsulates the map of the game.
Definition: location.hpp:38
bool valid() const
Definition: location.hpp:89
int wml_y() const
Definition: location.hpp:154
static const map_location & null_location()
Definition: location.hpp:81
int wml_x() const
Definition: location.hpp:153
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:142
std::vector< map_location > & steps
Definition: pathfind.hpp:187
Object which contains all the possible locations a unit can move to, with associated best routes to t...
Definition: pathfind.hpp:73
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
static constexpr 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:2093
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:1486
Various functions that implement the undoing (and redoing) of in-game commands.
#define e
#define h
#define a