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