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