The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
playsingle_controller.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 * Logic for single-player game.
20 */
21
23
24#include "actions/undo.hpp"
25#include "ai/manager.hpp"
26#include "ai/testing.hpp"
30#include "game_events/pump.hpp"
31#include "gettext.hpp"
35#include "log.hpp"
36#include "map/label.hpp"
37#include "map/map.hpp"
39#include "replay_controller.hpp"
40#include "replay_helper.hpp"
41#include "resources.hpp"
42#include "saved_game.hpp"
43#include "savegame.hpp"
45#include "sound.hpp"
46#include "soundsource.hpp"
47#include "synced_context.hpp"
48#include "video.hpp"
51
52#include <thread>
53
54static lg::log_domain log_aitesting("ai/testing");
55#define LOG_AIT LOG_STREAM(info, log_aitesting)
56// If necessary, this define can be replaced with `#define LOG_AIT std::cout` to restore previous behavior
57
58static lg::log_domain log_engine("engine");
59#define ERR_NG LOG_STREAM(err, log_engine)
60#define LOG_NG LOG_STREAM(info, log_engine)
61#define DBG_NG LOG_STREAM(debug, log_engine)
62
63static lg::log_domain log_enginerefac("enginerefac");
64#define LOG_RG LOG_STREAM(info, log_enginerefac)
65
67 : play_controller(level, state_of_game)
68 , cursor_setter_(cursor::NORMAL)
69 , end_turn_requested_(false)
70 , ai_fallback_(false)
71 , replay_controller_()
72{
73 // upgrade hotkey handler to the sp (whiteboard enabled) version
74 hotkey_handler_ = std::make_unique<hotkey_handler>(*this, saved_game_);
75
76
77 plugins_context_->set_accessor_string("level_result", std::bind(&playsingle_controller::describe_result, this));
78 plugins_context_->set_accessor_int("turn", std::bind(&play_controller::turn, this));
79}
80
81///Defined here to reduce file includes.
83
84
86{
87 if(!is_regular_game_end()) {
88 return "NONE";
89 } else if(get_end_level_data().is_victory) {
90 return "VICTORY";
91 } else {
92 return "DEFEAT";
93 }
94}
95
97{
98 LOG_NG << "Initializing GUI... " << timer();
99 // If we are retarting replay from linger mode.
102
103 // Scroll to the starting position of the first team. If there is a
104 // human team, use that team; otherwise use team 1. If the map defines
105 // a starting position for the selected team, scroll to that tile. Note
106 // this often does not matter since many scenario start with messages,
107 // which will usually scroll to the speaker. Also note that the map
108 // does not necessarily define the starting positions. While usually
109 // best to use the map, the scenarion may explicitly set the positions,
110 // overriding those found in the map (if any).
111 if(map_start_.valid()) {
112 gui_->scroll_to_tile(map_start_, game_display::WARP, false);
113 LOG_NG << "Found good stored ui location " << map_start_;
114 } else {
115
116 int scroll_team = find_viewing_side();
117 if(scroll_team == 0) {
118 scroll_team = 1;
119 }
120
121 map_location loc(get_map().starting_position(scroll_team));
122 if((loc.x >= 0) && (loc.y >= 0)) {
123 gui_->scroll_to_tile(loc, game_display::WARP);
124 LOG_NG << "Found bad stored ui location " << map_start_ << " using side starting location " << loc;
125 } else {
126 LOG_NG << "Found bad stored ui location";
127 }
128 }
129
130 // Fade in
131 gui_->set_prevent_draw(false);
132 gui_->queue_repaint();
133 if(!video::headless() && !video::testing()) {
134 gui_->fade_to({0,0,0,0}, std::chrono::milliseconds{500});
135 } else {
136 gui_->set_fade({0,0,0,0});
137 }
138
140}
141
143{
144 gui_->labels().read(level);
146
147 // Read sound sources
148 assert(soundsources_manager_ != nullptr);
149 for(const config& s : level.child_range("sound_source")) {
150 try {
152 soundsources_manager_->add(spec);
153 } catch(const bad_lexical_cast&) {
154 ERR_NG << "Error when parsing sound_source config: bad lexical cast.";
155 ERR_NG << "sound_source config was: " << s.debug();
156 ERR_NG << "Skipping this sound source...";
157 }
158 }
159
160 // At the beginning of the scenario, save a snapshot as replay_start
163 }
164
165 fire_preload();
167
168 start_game();
169 gamestate_->player_number_ = skip_empty_sides(gamestate_->player_number_).side_num;
170
171 if(!get_teams().empty()) {
173 if(gamestate().in_phase(game_data::TURN_PLAYING)) {
175 }
176 }
177
179 // This won't cause errors later but we should notify the user about it in case he didn't knew it.
181 // TODO: find a better title
182 _("Game Error"),
183 _("This multiplayer game uses an alternative random mode, if you don’t know what this message means, then "
184 "most likely someone is cheating or someone reloaded a corrupt game."));
185 }
186}
187
189{
190 const int sides = static_cast<int>(get_teams().size());
191 const int max = side_num + sides;
192
193 for (; side_num != max; ++side_num) {
194 int side_num_mod = modulo(side_num, sides, 1);
195 if(!gamestate().board_.get_team(side_num_mod).is_empty()) {
196 return { side_num_mod, side_num_mod != side_num };
197 }
198 }
199 return { side_num, true };
200}
201
203{
204 //TODO: Its still unclear to me when end_turn_requested_ should be reset, i guess the idea is
205 // in particular that in rare cases when the player looses control at the same time
206 // as he presses "end turn" and then regains control back, the "end turn" should be discarded?
207 //One of the main reasonsy why this is here is probably also that play_controller has no access to it.
209
211
213 play_side();
214 assert(is_regular_game_end() || gamestate().in_phase(game_data::TURN_ENDED));
215 }
216
219 }
220
223 do_end_level();
225 }
226
227 if (gamestate().in_phase(game_data::GAME_ENDED)) {
229 maybe_linger();
230 }
231}
232
234{
235 do {
236 if(std::find_if(get_teams().begin(), get_teams().end(), [](const team& t) { return !t.is_empty(); }) == get_teams().end()){
237 throw game::game_error("The scenario has no (non-empty) sides defined");
238 }
240
242 if(is_regular_game_end()) {
243 return;
244 }
245 // This flag can be set by derived classes (in overridden functions).
246 player_type_changed_ = false;
247
248
250
251 if(is_regular_game_end()) {
252 return;
253 }
254 } while(player_type_changed_);
255
256 // Keep looping if the type of a team (human/ai/networked) has changed mid-turn
258}
259
261{
262 if(is_regular_game_end()) {
263 return;
264 }
265
266 /// Make a copy, since the [end_turn] was already sent to to server any changes to
267 // next_player_number by wml would cause OOS otherwise.
268 int next_player_number_temp = gamestate_->next_player_number_;
269 whiteboard_manager_->on_finish_side_turn(current_side());
270
272 if(is_regular_game_end()) {
273 return;
274 }
275
276 auto [next_player_number, new_turn] = skip_empty_sides(next_player_number_temp);
277
278 if(new_turn) {
279 finish_turn();
280 if(is_regular_game_end()) {
281 return;
282 }
283 // Time has run out
285 if(is_regular_game_end()) {
286 return;
287 }
289 }
290
291 gamestate_->player_number_ = next_player_number;
292 if(current_team().is_empty()) {
293 // We don't support this case (turn end events emptying the next sides controller) since the server cannot handle it.
294 throw game::game_error("Empty side after new turn events");
295 }
296
297 if(new_turn) {
298 whiteboard_manager_->on_gamestate_change();
299 gui_->new_turn();
300 gui_->invalidate_game_status();
301 }
305 end_turn_requested_ = false;
307}
308
310{
311 LOG_NG << "starting main loop... " << timer();
312
314 while(!(gamestate().in_phase(game_data::GAME_ENDED) && end_linger() )) {
315 try {
316 play_some();
317 } catch(const reset_gamestate_exception& ex) {
318 boost::dynamic_bitset<> local_players;
319 local_players.resize(get_teams().size(), true);
320 // Preserve side controllers, because we won't get the side controoller updates again when replaying.
321 for(std::size_t i = 0; i < local_players.size(); ++i) {
322 local_players[i] = get_teams()[i].is_local();
323 }
324
325 if(ex.stats_) {
326 // "Back to turn"
328 } else {
329 // "Reset Replay To start"
331 }
332
333 reset_gamestate(*ex.level, (*ex.level)["replay_pos"].to_int());
334
335 for(std::size_t i = 0; i < local_players.size(); ++i) {
336 resources::gameboard->teams()[i].set_local(local_players[i]);
337 }
338
339 // Restore the play-list saved with the level, as play_scenario() does on load.
340 // The end-of-level music empties it, so without this the music stops once the
341 // victory/defeat track finishes - GitHub #10968
342 for(const config& m : ex.level->child_range("music")) {
344 }
345
347
349
350 if(replay_controller_ == nullptr) {
351 replay_controller_ = std::make_unique<replay_controller>(*this, false, ex.level, [this]() { on_replay_end(false); });
352 }
353
354 if(ex.start_replay) {
355 replay_controller_->play_replay();
356 }
357 }
358 } // end for loop
359}
360
362{
364 exit(0);
365 }
366 const bool is_victory = get_end_level_data().is_victory;
367
369
370 const end_level_data& end_level = get_end_level_data();
371
372 if(get_teams().empty()) {
373 // this is probably only a story scenario, i.e. has its endlevel in the prestart event
374 return;
375 }
376
377
378 pump().fire(is_victory ? "local_victory" : "local_defeat");
379
380 { // Block for set_scontext_synced_base
382 pump().fire(end_level.proceed_to_next_level ? level_result::victory : level_result::defeat);
383 pump().fire("scenario_end");
384 }
385
386 if(end_level.proceed_to_next_level) {
388 }
389
390 if(is_observer()) {
391 gui2::show_transient_message(_("Game Over"), _("The game is over."));
392 }
393
394 // If we're a player, and the result is victory/defeat, then send
395 // a message to notify the server of the reason for the game ending.
397 "info", config {
398 "type", "termination",
399 "condition", "game over",
400 "result", is_victory ? level_result::victory : level_result::defeat,
401 },
402 });
403
404 // Play victory music once all victory events
405 // are finished, if we aren't observers and the
406 // carryover dialog isn't disabled.
407 //
408 // Some scenario authors may use 'continue'
409 // result for something that is not story-wise
410 // a victory, so let them use [music] tags
411 // instead should they want special music.
412 const std::string& end_music = select_music(is_victory);
413 if((!is_victory || end_level.transient.carryover_report) && !end_music.empty()) {
415 sound::play_music_once(end_music);
416 }
417
419}
420
421level_result::type playsingle_controller::play_scenario(const config& level)
422{
423 LOG_NG << "in playsingle_controller::play_scenario()...";
424
425 // Start music.
426 for(const config& m : level.child_range("music")) {
428 }
429
431
433 // Combine all the [story] tags into a single config. Handle this here since
434 // storyscreen::controller doesn't have a default constructor.
435 config cfg;
436 for(const auto& iter : level.child_range("story")) {
437 cfg.append_children(iter);
438 }
439
440 if(!cfg.empty()) {
442 }
443 }
444
445
446 try {
447 play_scenario_init(level);
448 // clears level config (the intention was probably just to save some ram),
449 // Note: this might clear 'level', so don't use level after this.
451
453
454 // TODO: would it be better if the is_networked_mp() check was done in is_observer() ?
455 if(is_networked_mp() && is_observer()) {
456 return level_result::type::observer_end;
457 }
458 return level_result::get_enum(get_end_level_data().test_result).value_or(get_end_level_data().is_victory ? level_result::type::victory : level_result::type::defeat);
459 } catch(const savegame::load_game_exception&) {
460 // Loading a new game is effectively a quit.
462 throw;
463 } catch(const wesnothd_error& e) {
464 scoped_savegame_snapshot snapshot(*this);
465 savegame::ingame_savegame save(saved_game_, prefs::get().save_compression_format());
466 if(e.message == "") {
468 _("A network disconnection has occurred, and the game cannot continue. Do you want to save the game?"),
470 } else {
472 _("This game has been ended.\nReason: ") + e.message + _("\nDo you want to save the game?"),
474 }
475
476 if(dynamic_cast<const ingame_wesnothd_error*>(&e) || dynamic_cast<const leavegame_wesnothd_error*>(&e)) {
477 return level_result::type::quit;
478 } else {
479 throw;
480 }
481 }
482}
483
485{
488 using namespace std::chrono_literals;
489 std::this_thread::sleep_for(10ms);
490 }
491}
492
494{
496 replay_controller_->play_side_impl();
497
499 replay_controller_.reset();
500 }
501 } else if((current_team().is_local_human() && current_team().is_proxy_human())) {
502 LOG_NG << "is human...";
503 // If a side is dead end the turn, but play at least side=1's
504 // turn in case all sides are dead
505 if(gamestate().board_.side_units(current_side()) == 0 && !(get_units().empty() && current_side() == 1)) {
507 }
508
509
513 }
514
517 }
518
519 LOG_NG << "human finished turn...";
520 } else if(current_team().is_local_ai() || (current_team().is_local_human() && current_team().is_droid())) {
521 play_ai_turn();
522 } else if(current_team().is_network()) {
524 } else if(current_team().is_local_human() && current_team().is_idle()) {
525 end_turn_enable(false);
528
531 }
532 } else {
533 // we should have skipped over empty controllers before so this shouldn't be possible
534 ERR_NG << "Found invalid side controller " << side_controller::get_string(current_team().controller()) << " ("
535 << side_proxy_controller::get_string(current_team().proxy_controller()) << ") for side " << current_team().side();
536 }
537}
538
540{
541 log_scope("player turn");
542 assert(!is_linger_mode());
544 return;
545 }
546
547 if(!did_autosave_this_turn_ && !game_config::disable_autosave && prefs::get().auto_save_max() > 0) {
549 scoped_savegame_snapshot snapshot(*this);
550 savegame::autosave_savegame save(saved_game_, prefs::get().save_compression_format());
552 }
553
554 if(prefs::get().turn_bell()) {
556 }
557}
558
560{
561 if(prefs::get().turn_dialog() && !is_regular_game_end()) {
562 blindfold b(*gui_, true); // apply a blindfold for the duration of this dialog
563 gui_->queue_rerender();
564 std::string message = _("It is now $name|’s turn");
565 utils::string_map symbols;
566 symbols["name"] = gamestate().board_.get_team(current_side()).side_name();
567 message = utils::interpolate_variables_into_string(message, &symbols);
568 gui2::show_transient_message("", message);
569 }
570}
571
573{
575 return;
576 }
577
578 try {
580 } catch(const return_to_play_side_exception&) {
581 }
582}
583
585{
587
588 if(!prefs::get().disable_auto_moves()) {
590 }
591
592 end_turn_enable(true);
593
597 }
598}
599
601{
602 if(is_linger_mode()) {
603 // If we need to set the status depending on the completion state
604 // the key to it is here.
605 gui_->set_game_mode(game_display::LINGER);
606 // change the end-turn button text from "End Turn" to "End Scenario"
607 gui_->get_theme().refresh_title2("button-endturn", "title2");
608
609 if(get_end_level_data().transient.reveal_map) {
610 // Change the view of all players and observers
611 // to see the whole map regardless of shroud and fog.
612 update_gui_to_player(gui_->viewing_team_index(), true);
613 }
614 } else {
615 gui_->set_game_mode(game_display::RUNNING);
616 // change the end-turn button text from "End Scenario" to "End Turn"
617 gui_->get_theme().refresh_title2("button-endturn", "title");
618 }
619 // Also checcks whether the button can be pressed.
620 gui_->queue_rerender();
621}
622
624{
625 LOG_NG << "beginning end-of-scenario linger";
626
627 // Make all of the able-to-move units' orbs consistently red
629
631
633 replay_controller_->play_side_impl();
635 replay_controller_.reset();
636 }
637 }
638 while(!end_linger()) {
639 play_slice();
640 }
641
642 LOG_NG << "ending end-of-scenario linger";
643}
644
646{
647 // TODO: this is really only needed to refresh the visual state of the buttons on each turn.
648 // It looks like we can put it in play_side_impl, but it definitely should be removed from
649 // here since other code already takes care of actually enabling/disabling the end turn button.
651}
652
654{
655 // Clear moves from the GUI.
656 gui_->set_route(nullptr);
657 gui_->unhighlight_reach();
658}
659
661{
662 LOG_NG << "is ai...";
663
664 end_turn_enable(false);
665
666 const cursor::setter cursor_setter(cursor::WAIT);
667
668 // Correct an oddball case where a human could have left delayed shroud
669 // updates on before giving control to the AI. (The AI does not bother
670 // with the undo stack, so it cannot delay shroud updates.)
671 team& cur_team = current_team();
672 if(!cur_team.auto_shroud_updates()) {
673 // We just took control, so the undo stack is empty. We still need
674 // to record this change for the replay though.
676 }
677
678 undo_stack().clear();
679
680 try {
681 try {
684 }
685 } catch(const return_to_play_side_exception&) {
686 } catch(const fallback_ai_to_human_exception&) {
689 ai_fallback_ = true;
690 }
691 } catch(...) {
692 DBG_NG << "Caught exception playing ai turn: " << utils::get_unknown_exception_type();
693 throw;
694 }
695
698 }
699}
700
701/**
702 * Will handle sending a networked notification in descendent classes.
703 */
705{
706 gui_->get_chat_manager().add_chat_message(std::chrono::system_clock::now(), "Wesnoth", 0,
707 "This side is in an idle state. To proceed with the game, the host must assign it to another controller.",
709}
710
711/**
712 * Will handle networked turns in descendent classes.
713 */
715{
716 // There should be no networked sides in single-player.
717 ERR_NG << "Networked team encountered by playsingle_controller.";
718}
719
721{
722 if(name == "ai_user_interact") {
723 play_slice();
724 }
725}
726
728{
729 if(is_linger_mode()) {
730 end_turn_requested_ = true;
731 } else if(!is_browsing() && menu_handler_.end_turn(current_side())) {
733 }
734}
735
741
746
748{
749 if(!get_teams().empty()) {
750 const team& t = gui_->viewing_team();
751
752 if(!is_regular_game_end() && !is_browsing() && t.objectives_changed()) {
754 }
755 }
756}
757
759{
760 // mouse_handler expects at least one team for linger mode to work.
761 assert(is_regular_game_end());
762 linger();
763 end_turn_requested_ = true;
764}
765
767{
768 // We cannot add [end_turn] to the recorder while executing another action.
770
771 if(!gamestate().in_phase(game_data::TURN_ENDED)) {
772 assert(end_turn_requested_);
773 assert(current_team().is_local());
774 assert(gamestate().in_phase(game_data::TURN_PLAYING));
775 // TODO: we should also send this immediately.
776 resources::recorder->end_turn(gamestate_->next_player_number_);
778
779 }
780
781
782 assert(gamestate().in_phase(game_data::TURN_ENDED));
783
784 if(ai_fallback_) {
786 ai_fallback_ = false;
787 }
788}
789
790bool playsingle_controller::is_team_visible(int team_num, bool observer) const
791{
792 const team& t = gamestate().board_.get_team(team_num);
793 if(observer) {
794 return !t.get_disallow_observers() && !t.is_empty();
795 } else {
796 return t.is_local_human() && !t.is_idle();
797 }
798}
799
801{
802 const int num_teams = get_teams().size();
803 const bool observer = is_observer();
804
805 for(int i = 0; i < num_teams; i++) {
806 const int team_num = modulo(current_side() + i, num_teams, 1);
807 if(is_team_visible(team_num, observer)) {
808 return team_num;
809 }
810 }
811
812 return 0;
813}
814
816{
817 if(replay_controller_ && replay_controller_->is_controlling_view()) {
818 replay_controller_->update_viewing_player();
819 } else if(int side_num = find_viewing_side()) {
820 if(side_num != gui_->viewing_team().side() || gui_->show_everything()) {
821 update_gui_to_player(side_num - 1);
822 }
823 }
824}
825
827{
828 if(replay_controller_ && replay_controller_->allow_reset_replay()) {
829 replay_controller_->stop_replay();
830 throw reset_gamestate_exception(replay_controller_->get_reset_state(), {}, false);
831 } else {
832 ERR_NG << "received invalid reset replay";
833 }
834}
835
837{
838 replay_controller_ = std::make_unique<replay_controller>(
839 *this,
840 true,
841 std::make_shared<config>(saved_game_.get_replay_starting_point()),
842 std::bind(&playsingle_controller::on_replay_end, this, is_unit_test)
843 );
844
845 if(is_unit_test) {
846 replay_controller_->play_replay();
847 }
848}
849
851{
853 return true;
854 } else if(gamestate().in_phase(game_data::TURN_ENDED)) {
855 return true;
856 } else if((gamestate().in_phase(game_data::TURN_STARTING_WAITING) || end_turn_requested_) && !replay_controller_ && current_team().is_local() && !current_team().is_idle()) {
857 // When we are a locally controlled side and havent done init_side yet also return to play_side
858 return true;
859 } else {
860 return false;
861 }
862}
863
865{
866 if(is_networked_mp()) {
867 // we are using the "Back to turn (replay)" feature
868 // And have reached the current gamestate: end the replay and continue normally.
870 } else if(is_unit_test) {
871 replay_controller_->return_to_play_side();
872 if(!is_regular_game_end()) {
874 e.proceed_to_next_level = false;
875 e.is_victory = false;
877 }
878 } else {
879 replay_controller_->stop_replay();
880 }
881}
map_location loc
Definition move.cpp:172
#define ERR_NG
#define DBG_NG
#define LOG_NG
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
double t
void clear()
Clears the stack of undoable (and redoable) actions.
Definition undo.cpp:131
static manager & get_singleton()
Definition manager.hpp:140
void play_turn(side_number side)
Plays a turn for the specified side using its active AI.
Definition manager.cpp:581
static void log_game_end()
Definition testing.cpp:101
static void log_game_start()
Definition testing.cpp:90
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void append_children(const config &cfg)
Adds children from cfg.
Definition config.cpp:167
bool empty() const
Definition config.cpp:823
virtual void play_slice()
void execute_gotos(mouse_handler &mousehandler, int side_num)
bool end_turn(int side_num)
virtual const std::vector< team > & teams() const override
void heal_all_survivors()
team & get_team(int i)
void set_all_units_user_end_turn()
void set_end_turn_forced(bool v)
void set_phase(PHASE phase)
@ GAME_ENDED
The game has ended and the user is observing the final state "lingering" The game can be saved here.
@ TURN_PLAYING
The User is controlling the game and invoking actions The game can be saved here.
Definition game_data.hpp:93
@ TURN_ENDED
The turn_end, side_turn_end etc [events] are fired next phase: TURN_STARTING_WAITING (default),...
Definition game_data.hpp:96
@ TURN_STARTING_WAITING
we are waiting for the turn to start.
Definition game_data.hpp:86
@ GAME_ENDING
The victory etc.
Definition game_data.hpp:99
static PHASE read_phase(const config &cfg)
bool end_turn_forced() const
@ RUNNING
no linger overlay, show fog and shroud.
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition pump.cpp:399
game_board board_
game_data gamedata_
static void display(const std::string &scenario_name, const config &story)
actions::undo_list & undo_stack()
const std::string & select_music(bool victory) const
config to_config() const
Builds the snapshot config from members and their respective configs.
const end_level_data & get_end_level_data() const
std::unique_ptr< hotkey_handler > hotkey_handler_
virtual void init_gui()
const unit_map & get_units() const
std::unique_ptr< game_state > gamestate_
void show_objectives() const
events::menu_handler menu_handler_
void fire_preload()
preload events cannot be synced
bool is_linger_mode() const
const gamemap & get_map() const
void set_end_level_data(const end_level_data &data)
bool is_observer() const
void reset_gamestate(const config &level, int replay_pos)
bool is_skipping_story() const
bool is_regular_game_end() const
saved_game & saved_game_
hotkey::command_executor * get_hotkey_command_executor() override
Optionally get a command executor to handle context menu events.
std::unique_ptr< game_display > gui_
void maybe_do_init_side()
Called by turn_info::process_network_data() or init_side() to call do_init_side() if necessary.
bool is_browsing() const override
std::vector< team > & get_teams()
virtual void send_to_wesnothd(const config &, const std::string &="unknown") const
std::unique_ptr< soundsource::manager > soundsources_manager_
int current_side() const
Returns the number of the side whose turn it is.
virtual bool is_networked_mp() const
bool is_skipping_replay() const
saved_game & get_saved_game()
bool did_autosave_this_turn_
Whether we did init sides in this session (false = we did init sides before we reloaded the game).
bool player_type_changed_
true when the controller of the currently playing side has changed.
std::size_t turn() const
map_location map_start_
void update_gui_to_player(const int team_index, const bool observe=false)
Changes the UI for this client to the passed side index.
events::mouse_handler mouse_handler_
std::unique_ptr< plugins_context > plugins_context_
game_events::wml_event_pump & pump()
const auto & timer() const
persist_manager persist_
std::shared_ptr< wb::manager > whiteboard_manager_
virtual void check_time_over()
t_string get_scenario_name() const
game_state & gamestate()
virtual void play_network_turn()
Will handle networked turns in descendent classes.
virtual void init_gui() override
void on_replay_end(bool is_unit_test)
std::string describe_result() const
std::unique_ptr< replay_controller > replay_controller_
non-null when replay mode in active, is used in singleplayer and for the "back to turn" feature in mu...
bool ai_fallback_
true when the current side is actually an ai side but was taken over by a human (usually for debuggin...
virtual void check_objectives() override
ses_result skip_empty_sides(int side_num)
Calculates the current side, starting at side_num that is non-empty.
bool is_team_visible(int team_num, bool observer) const
level_result::type play_scenario(const config &level)
virtual bool should_return_to_play_side() const override
~playsingle_controller()
Defined here to reduce file includes.
playsingle_controller(const config &level, saved_game &state_of_game)
virtual void handle_generic_event(const std::string &name) override
virtual void do_idle_notification()
Will handle sending a networked notification in descendent classes.
bool end_turn_requested_
true iff the user has pressed the end turn button this turn.
int find_viewing_side() const override
returns 0 if no such team was found.
void play_scenario_init(const config &level)
void enable_replay(bool is_unit_test=false)
static prefs & get()
static config get_auto_shroud(bool turned_on, bool block_undo=true)
Records that the player has toggled automatic shroud updates.
void end_turn(int next_player_number)
Definition replay.cpp:289
Exception used to escape form the ai or ui code to playsingle_controller::play_side.
config & replay_start()
game_classification & classification()
void remove_snapshot()
const config & get_replay_starting_point()
statistics_record::campaign_stats_t & statistics()
Class for autosaves.
Definition savegame.hpp:257
void autosave(const bool disable_autosave, const int autosave_max, const int infinite_autosaves)
Definition savegame.cpp:511
Class for "normal" midgame saves.
Definition savegame.hpp:230
Exception used to signal that the user has decided to abort a game, and to load another game instead.
Definition savegame.hpp:88
bool save_game_interactive(const std::string &message, DIALOG_TYPE dialog_type)
Save a game interactively through the savegame dialog.
Definition savegame.cpp:291
Sound source info class.
static bool run_and_store(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
const std::string & side_name() const
Definition team.hpp:334
int side() const
Definition team.hpp:182
bool auto_shroud_updates() const
Definition team.hpp:366
void make_ai()
Definition team.hpp:301
void make_human()
Definition team.hpp:300
bool get_disallow_observers() const
Definition team.hpp:368
const config * cfg
Contains the exception interfaces used to signal completion of a scenario, campaign or turn.
static std::string _(const char *str)
Definition gettext.hpp:100
An extension of play_controller::hotkey_handler, which has support for SP wesnoth features like white...
Standard logging facilities (interface).
#define log_scope(description)
Definition log.hpp:275
constexpr T modulo(T num, int mod, T min=0)
Definition math.hpp:60
@ WAIT
Definition cursor.hpp:28
bool disable_autosave
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.
const int INFINITE_AUTO_SAVES
game_board * gameboard
Definition resources.cpp:20
replay * recorder
Definition resources.cpp:28
void empty_playlist()
Definition sound.cpp:608
void play_music_config(const config &music_node, bool allow_interrupt_current_track, int i)
Definition sound.cpp:687
void play_music_once(const std::string &file)
Definition sound.cpp:599
void commit_music_changes()
Definition sound.cpp:795
void play_bell(const std::string &files)
Definition sound.cpp:993
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
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
bool headless()
The game is running headless.
Definition video.cpp:146
bool testing()
The game is running unit tests.
Definition video.cpp:151
static lg::log_domain log_engine("engine")
static lg::log_domain log_aitesting("ai/testing")
static lg::log_domain log_enginerefac("enginerefac")
Define the game's event mechanism.
Thrown when a lexical_cast fails.
Additional information on the game outcome which can be provided by WML.
bool proceed_to_next_level
whether to proceed to the next scenario, equals is_victory in sp.
transient_end_level transient
Error used for any general game error, e.g.
We received invalid data from wesnothd during a game This means we cannot continue with the game but ...
Encapsulates the map of the game.
Definition location.hpp:46
bool valid() const
Definition location.hpp:111
std::shared_ptr< config > stats_
std::shared_ptr< config > level
void read(const config &cfg, bool append=false)
void clear_current_scenario()
Delete the current scenario from the stats.
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
bool carryover_report
Should a summary of the scenario outcome be displayed?
bool linger_mode
Should linger mode be invoked?
An error occurred during when trying to communicate with the wesnothd server.
static map_location::direction s
Gather statistics important for AI testing and output them.
Various functions that implement the undoing (and redoing) of in-game commands.
#define i
#define e
#define b