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