diff --git a/src/achievement.cpp b/src/achievement.cpp index 79f0771f1dca0..08f2ef0f28e85 100644 --- a/src/achievement.cpp +++ b/src/achievement.cpp @@ -561,7 +561,7 @@ std::string achievement_tracker::ui_text() const achievements_tracker::achievements_tracker( stats_tracker &stats, - const std::function &achievement_attained_callback ) : + const std::function &achievement_attained_callback ) : stats_( &stats ), achievement_attained_callback_( achievement_attained_callback ) {} @@ -594,7 +594,7 @@ void achievements_tracker::report_achievement( const achievement *a, achievement } ); if( comp == achievement_completion::completed ) { - achievement_attained_callback_( a ); + achievement_attained_callback_( a, is_enabled() ); } trackers_.erase( tracker_it ); } @@ -662,6 +662,7 @@ void achievements_tracker::notify( const cata::event &e ) void achievements_tracker::serialize( JsonOut &jsout ) const { jsout.start_object(); + jsout.member( "enabled", enabled_ ); jsout.member( "initial_achievements", initial_achievements_ ); jsout.member( "achievements_status", achievements_status_ ); jsout.end_object(); @@ -670,6 +671,7 @@ void achievements_tracker::serialize( JsonOut &jsout ) const void achievements_tracker::deserialize( JsonIn &jsin ) { JsonObject jo = jsin.get_object(); + jo.read( "enabled", enabled_ ) || ( enabled_ = true ); jo.read( "initial_achievements", initial_achievements_ ); jo.read( "achievements_status", achievements_status_ ); diff --git a/src/achievement.h b/src/achievement.h index 82d196fc44418..185b0fa6835c7 100644 --- a/src/achievement.h +++ b/src/achievement.h @@ -166,7 +166,7 @@ class achievements_tracker : public event_subscriber achievements_tracker( stats_tracker &, - const std::function &achievement_attained_callback ); + const std::function &achievement_attained_callback ); ~achievements_tracker() override; // Return all scores which are valid now and existed at game start @@ -177,6 +177,12 @@ class achievements_tracker : public event_subscriber achievement_completion is_completed( const string_id & ) const; bool is_hidden( const achievement * ) const; std::string ui_text_for( const achievement * ) const; + bool is_enabled() const { + return enabled_; + } + void set_enabled( bool enabled ) { + enabled_ = enabled; + } void clear(); void notify( const cata::event & ) override; @@ -187,7 +193,8 @@ class achievements_tracker : public event_subscriber void init_watchers(); stats_tracker *stats_ = nullptr; - std::function achievement_attained_callback_; + bool enabled_ = true; + std::function achievement_attained_callback_; std::unordered_set> initial_achievements_; // Class invariant: each valid achievement has exactly one of a watcher diff --git a/src/debug_menu.cpp b/src/debug_menu.cpp index 25a75fdb8be17..9f84806a8834c 100644 --- a/src/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -22,6 +22,7 @@ #include #include +#include "achievement.h" #include "action.h" #include "artifact.h" #include "avatar.h" @@ -147,6 +148,7 @@ enum debug_menu_index { DEBUG_BENCHMARK, DEBUG_OM_TELEPORT, DEBUG_TRAIT_GROUP, + DEBUG_ENABLE_ACHIEVEMENTS, DEBUG_SHOW_MSG, DEBUG_CRASH_GAME, DEBUG_MAP_EXTRA, @@ -245,6 +247,17 @@ static int info_uilist( bool display_all_entries = true ) return uilist( _( "Info…" ), uilist_initializer ); } +static int game_uilist() +{ + std::vector uilist_initializer = { + { uilist_entry( DEBUG_ENABLE_ACHIEVEMENTS, true, 'a', _( "Enable achievements" ) ) }, + { uilist_entry( DEBUG_SHOW_MSG, true, 'd', _( "Show debug message" ) ) }, + { uilist_entry( DEBUG_CRASH_GAME, true, 'C', _( "Crash game (test crash handling)" ) ) }, + }; + + return uilist( _( "Game…" ), uilist_initializer ); +} + static int teleport_uilist() { const std::vector uilist_initializer = { @@ -304,6 +317,7 @@ static int debug_menu_uilist( bool display_all_entries = true ) if( display_all_entries ) { const std::vector debug_menu = { { uilist_entry( DEBUG_QUIT_NOSAVE, true, 'Q', _( "Quit to main menu" ) ) }, + { uilist_entry( 6, true, 'g', _( "Game…" ) ) }, { uilist_entry( 2, true, 's', _( "Spawning…" ) ) }, { uilist_entry( 3, true, 'p', _( "Player…" ) ) }, { uilist_entry( 4, true, 't', _( "Teleport…" ) ) }, @@ -345,6 +359,9 @@ static int debug_menu_uilist( bool display_all_entries = true ) case 5: action = map_uilist(); break; + case 6: + action = game_uilist(); + break; default: return group; @@ -1108,6 +1125,25 @@ void debug() { bool debug_menu_has_hotkey = hotkey_for_action( ACTION_DEBUG, false ) != -1; int action = debug_menu_uilist( debug_menu_has_hotkey ); + + // For the "cheaty" options, disable achievements when used + achievements_tracker &achievements = g->achievements(); + static const std::unordered_set non_cheaty_options = { + DEBUG_SAVE_SCREENSHOT, + DEBUG_GAME_REPORT, + DEBUG_ENABLE_ACHIEVEMENTS, + DEBUG_BENCHMARK, + DEBUG_SHOW_MSG, + }; + bool should_disable_achievements = action >= 0 && !non_cheaty_options.count( action ); + if( should_disable_achievements && achievements.is_enabled() ) { + if( query_yn( "Using this will disable achievements. Proceed?" ) ) { + achievements.set_enabled( false ); + } else { + action = -1; + } + } + g->refresh_all(); avatar &u = g->u; map &m = g->m; @@ -1583,6 +1619,14 @@ void debug() case DEBUG_TRAIT_GROUP: trait_group::debug_spawn(); break; + case DEBUG_ENABLE_ACHIEVEMENTS: + if( achievements.is_enabled() ) { + popup( _( "Achievements are already enabled" ) ); + } else { + achievements.set_enabled( true ); + popup( _( "Achievements enabled" ) ); + } + break; case DEBUG_SHOW_MSG: debugmsg( "Test debugmsg" ); break; diff --git a/src/game.cpp b/src/game.cpp index 743a52c6dfc22..8aa109d7bfef7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -257,10 +257,12 @@ bool is_valid_in_w_terrain( const point &p ) return p.x >= 0 && p.x < TERRAIN_WINDOW_WIDTH && p.y >= 0 && p.y < TERRAIN_WINDOW_HEIGHT; } -static void achievement_attained( const achievement *a ) +static void achievement_attained( const achievement *a, bool achievements_enabled ) { - g->u.add_msg_if_player( m_good, _( "You completed the achievement \"%s\"." ), - a->name() ); + if( achievements_enabled ) { + g->u.add_msg_if_player( m_good, _( "You completed the achievement \"%s\"." ), + a->name() ); + } } // This is the main game set-up process. @@ -3065,6 +3067,11 @@ stats_tracker &game::stats() return *stats_tracker_ptr; } +achievements_tracker &game::achievements() +{ + return *achievements_tracker_ptr; +} + memorial_logger &game::memorial() { return *memorial_logger_ptr; diff --git a/src/game.h b/src/game.h index 746d920bdce62..dc5d42d37c1e3 100644 --- a/src/game.h +++ b/src/game.h @@ -949,6 +949,7 @@ class game event_bus &events(); stats_tracker &stats(); + achievements_tracker &achievements(); memorial_logger &memorial(); spell_events &spell_events_subscriber(); diff --git a/src/scores_ui.cpp b/src/scores_ui.cpp index 3261f563a1f83..0b49d01278e92 100644 --- a/src/scores_ui.cpp +++ b/src/scores_ui.cpp @@ -20,6 +20,11 @@ static std::string get_achievements_text( const achievements_tracker &achievements ) { + if( !achievements.is_enabled() ) { + return _( "Achievements are disabled, probably due to use of the debug menu. " + "If you only used the debug menu to work around a game bug, then you " + "can re-enable achievements via the debug menu (under the Game submenu)." ); + } std::string os; std::vector valid_achievements = achievements.valid_achievements(); valid_achievements.erase( diff --git a/tests/stats_tracker_test.cpp b/tests/stats_tracker_test.cpp index aeff91fe5436e..33d67ae19f5ab 100644 --- a/tests/stats_tracker_test.cpp +++ b/tests/stats_tracker_test.cpp @@ -460,7 +460,7 @@ TEST_CASE( "achievments_tracker", "[stats]" ) event_bus b; stats_tracker s; b.subscribe( &s ); - achievements_tracker a( s, [&]( const achievement * a ) { + achievements_tracker a( s, [&]( const achievement * a, bool /*achievements_enabled*/ ) { achievements_completed.emplace( a->id, a ); } ); b.subscribe( &a );