
Fixes a crash when playing a replay that includes a theme, when that theme doesn't have all of the buttons that the removed code expected. For example, this replay from SXRPG 5.2.3 has two replay themes, of which one lacks button-nextmove: * https://replays.wesnoth.org/1.14/20190701/SXRPG_TempleOfBones_Turn_8_(53897).bz2 All of the button::enable() calls were unnecessary logic, because the hotkey system will query replay_controller::can_execute_command() and enable or disable the buttons to match. However, the enable() calls appear to have had the side-effect of triggering the hotkey system to check the buttons' required states and redraw. The code in build_replay_theme() was unreachable, because replay_controller doesn't attach itself to the completely_redrawn event.
272 lines
7.8 KiB
C++
272 lines
7.8 KiB
C++
/*
|
|
Copyright (C) 2015 - 2018 by the Battle for Wesnoth Project
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
#include "replay_controller.hpp"
|
|
|
|
#include "game_config_manager.hpp"
|
|
#include "gettext.hpp"
|
|
#include "log.hpp"
|
|
#include "replay.hpp"
|
|
#include "resources.hpp"
|
|
#include "playsingle_controller.hpp"
|
|
|
|
static lg::log_domain log_engine("engine");
|
|
#define DBG_NG LOG_STREAM(debug, log_engine)
|
|
|
|
static lg::log_domain log_replay("replay");
|
|
#define DBG_REPLAY LOG_STREAM(debug, log_replay)
|
|
#define LOG_REPLAY LOG_STREAM(info, log_replay)
|
|
#define ERR_REPLAY LOG_STREAM(err, log_replay)
|
|
|
|
namespace
|
|
{
|
|
struct replay_play_nostop : public replay_controller::replay_stop_condition
|
|
{
|
|
replay_play_nostop() {}
|
|
virtual bool should_stop() { return false; }
|
|
};
|
|
|
|
struct replay_play_moves : public replay_controller::replay_stop_condition
|
|
{
|
|
int moves_todo_;
|
|
replay_play_moves(int moves_todo) : moves_todo_(moves_todo) {}
|
|
virtual void move_done() { --moves_todo_; }
|
|
virtual bool should_stop() { return moves_todo_ == 0; }
|
|
};
|
|
|
|
struct replay_play_turn : public replay_controller::replay_stop_condition
|
|
{
|
|
int turn_begin_;
|
|
int turn_current_;
|
|
replay_play_turn(int turn_begin) : turn_begin_(turn_begin), turn_current_(turn_begin) {}
|
|
virtual void new_side_turn(int , int turn) { turn_current_ = turn; }
|
|
virtual bool should_stop() { return turn_begin_ != turn_current_; }
|
|
};
|
|
|
|
struct replay_play_side : public replay_controller::replay_stop_condition
|
|
{
|
|
bool next_side_;
|
|
replay_play_side() : next_side_(false) {}
|
|
virtual void new_side_turn(int , int) { next_side_ = true; }
|
|
virtual bool should_stop() { return next_side_; }
|
|
};
|
|
}
|
|
|
|
replay_controller::replay_controller(play_controller& controller, bool control_view, const std::shared_ptr<config>& reset_state, const std::function<void()>& on_end_replay)
|
|
: controller_(controller)
|
|
, stop_condition_(new replay_stop_condition())
|
|
, disabler_()
|
|
, vision_()
|
|
, reset_state_(reset_state)
|
|
, on_end_replay_(on_end_replay)
|
|
, return_to_play_side_(false)
|
|
{
|
|
if(control_view) {
|
|
vision_ = HUMAN_TEAM;
|
|
}
|
|
controller_.get_display().get_theme().theme_reset_event().attach_handler(this);
|
|
controller_.get_display().create_buttons();
|
|
controller_.get_display().redraw_everything();
|
|
}
|
|
replay_controller::~replay_controller()
|
|
{
|
|
if(controller_.is_skipping_replay()) {
|
|
controller_.toggle_skipping_replay();
|
|
}
|
|
controller_.get_display().get_theme().theme_reset_event().detach_handler(this);
|
|
controller_.get_display().create_buttons();
|
|
controller_.get_display().redraw_everything();
|
|
controller_.get_display().create_buttons();
|
|
}
|
|
void replay_controller::add_replay_theme()
|
|
{
|
|
const config &theme_cfg = controller_.get_theme(game_config_manager::get()->game_config(), controller_.theme());
|
|
if (const config &res = theme_cfg.child("resolution"))
|
|
{
|
|
if (const config &replay_theme_cfg = res.child("replay")) {
|
|
controller_.get_display().get_theme().modify(replay_theme_cfg);
|
|
}
|
|
//Make sure we get notified if the theme is redrawn completely. That way we have
|
|
//a chance to restore the replay controls of the theme as well.
|
|
controller_.get_display().invalidate_theme();
|
|
}
|
|
}
|
|
|
|
void replay_controller::stop_replay()
|
|
{
|
|
stop_condition_.reset(new replay_stop_condition());
|
|
update_enabled_buttons();
|
|
}
|
|
|
|
void replay_controller::replay_next_turn()
|
|
{
|
|
stop_condition_.reset(new replay_play_turn(controller_.gamestate().tod_manager_.turn()));
|
|
update_enabled_buttons();
|
|
}
|
|
|
|
void replay_controller::replay_next_side()
|
|
{
|
|
stop_condition_.reset(new replay_play_side());
|
|
update_enabled_buttons();
|
|
}
|
|
|
|
void replay_controller::replay_next_move()
|
|
{
|
|
stop_condition_.reset(new replay_play_moves(1));
|
|
update_enabled_buttons();
|
|
}
|
|
|
|
//move all sides till stop/end
|
|
void replay_controller::play_replay()
|
|
{
|
|
stop_condition_.reset(new replay_play_nostop());
|
|
update_enabled_buttons();
|
|
}
|
|
|
|
void replay_controller::update_gui()
|
|
{
|
|
controller_.get_display().recalculate_minimap();
|
|
controller_.get_display().redraw_minimap();
|
|
controller_.get_display().invalidate_all();
|
|
controller_.get_display().redraw_everything();
|
|
}
|
|
|
|
void replay_controller::update_enabled_buttons()
|
|
{
|
|
controller_.get_display().invalidate_theme();
|
|
controller_.get_display().redraw_everything();
|
|
}
|
|
|
|
void replay_controller::handle_generic_event(const std::string& name)
|
|
{
|
|
// this is only attached to one event - the theme_reset_event
|
|
if(name == "theme_reset") {
|
|
add_replay_theme();
|
|
}
|
|
if(std::shared_ptr<gui::button> skip_animation_button = controller_.get_display().find_action_button("skip-animation")) {
|
|
skip_animation_button->set_check(controller_.is_skipping_replay());
|
|
}
|
|
}
|
|
|
|
bool replay_controller::recorder_at_end() const
|
|
{
|
|
return resources::recorder->at_end();
|
|
}
|
|
|
|
REPLAY_RETURN replay_controller::play_side_impl()
|
|
{
|
|
update_enabled_buttons();
|
|
while(!return_to_play_side_ && !static_cast<playsingle_controller&>(controller_).get_player_type_changed())
|
|
{
|
|
if(!stop_condition_->should_stop())
|
|
{
|
|
if(resources::recorder->at_end()) {
|
|
//Gather more replay data
|
|
on_end_replay_();
|
|
}
|
|
else {
|
|
REPLAY_RETURN res = do_replay(true);
|
|
if(res == REPLAY_FOUND_END_MOVE) {
|
|
stop_condition_->move_done();
|
|
}
|
|
if(res == REPLAY_FOUND_END_TURN) {
|
|
return res;
|
|
}
|
|
if(res == REPLAY_RETURN_AT_END) {
|
|
stop_replay();
|
|
}
|
|
if(res == REPLAY_FOUND_INIT_TURN)
|
|
{
|
|
stop_condition_->new_side_turn(controller_.current_side(), controller_.gamestate().tod_manager_.turn());
|
|
}
|
|
}
|
|
controller_.play_slice(false);
|
|
|
|
// Update the buttons once, on the transition from not-stopped to stopped.
|
|
if(stop_condition_->should_stop()) {
|
|
update_enabled_buttons();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Don't move the update_enabled_buttons() call here. This play_slice() should block
|
|
// until the next event occurs, but on X11/Linux update_enabled_buttons() seems to put
|
|
// an event in the queue, turning this into a busy loop.
|
|
controller_.play_slice(true);
|
|
}
|
|
}
|
|
return REPLAY_FOUND_END_MOVE;
|
|
}
|
|
bool replay_controller::can_execute_command(const hotkey::hotkey_command& cmd, int) const
|
|
{
|
|
hotkey::HOTKEY_COMMAND command = cmd.id;
|
|
|
|
switch(command) {
|
|
case hotkey::HOTKEY_REPLAY_SKIP_ANIMATION:
|
|
return true;
|
|
case hotkey::HOTKEY_REPLAY_SHOW_EVERYTHING:
|
|
case hotkey::HOTKEY_REPLAY_SHOW_EACH:
|
|
case hotkey::HOTKEY_REPLAY_SHOW_TEAM1:
|
|
return is_controlling_view();
|
|
//commands we only can do before the end of the replay
|
|
case hotkey::HOTKEY_REPLAY_STOP:
|
|
return !recorder_at_end();
|
|
case hotkey::HOTKEY_REPLAY_PLAY:
|
|
case hotkey::HOTKEY_REPLAY_NEXT_TURN:
|
|
case hotkey::HOTKEY_REPLAY_NEXT_SIDE:
|
|
case hotkey::HOTKEY_REPLAY_NEXT_MOVE:
|
|
//we have one events_disabler when starting the replay_controller and a second when entering the synced context.
|
|
return should_stop() && (events::commands_disabled <= 1 ) && !recorder_at_end();
|
|
case hotkey::HOTKEY_REPLAY_RESET:
|
|
return allow_reset_replay() && events::commands_disabled <= 1;
|
|
default:
|
|
assert(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void replay_controller::replay_show_everything()
|
|
{
|
|
vision_ = SHOW_ALL;
|
|
update_teams();
|
|
}
|
|
|
|
void replay_controller::replay_show_each()
|
|
{
|
|
vision_ = CURRENT_TEAM;
|
|
update_teams();
|
|
}
|
|
|
|
void replay_controller::replay_show_team1()
|
|
{
|
|
vision_ = HUMAN_TEAM;
|
|
update_teams();
|
|
}
|
|
|
|
void replay_controller::update_teams()
|
|
{
|
|
update_viewing_player();
|
|
controller_.get_display().invalidate_all();
|
|
update_gui();
|
|
}
|
|
|
|
void replay_controller::update_viewing_player()
|
|
{
|
|
assert(vision_);
|
|
controller_.update_gui_to_player(vision_ == HUMAN_TEAM ? controller_.gamestate().first_human_team_ : controller_.current_side() - 1, *vision_ == SHOW_ALL);
|
|
}
|
|
|
|
bool replay_controller::see_all()
|
|
{
|
|
return vision_ == SHOW_ALL;
|
|
}
|