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