Skip to content

Commit

Permalink
Achievements recorded on save, not death (CleverRaven#71327)
Browse files Browse the repository at this point in the history
* This commit would add a class "past_achievements_info" that is used to track achievement IDs that are attained during gameplay. That was being done by memorials, so your achievements were not recorded until you died.

* Add missing includes, rename isCompleted to is_completed

* Add handling for memorial achievements, remove unused struct

* Fix comments

* Reorder assure_dir_exists and migrate_memorial

* Fix auto kv to const&

---------

Co-authored-by: tm <[email protected]>
  • Loading branch information
yoink3331 and tm authored Jan 31, 2024
1 parent 8881782 commit 622f089
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 17 deletions.
20 changes: 20 additions & 0 deletions src/achievement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "cata_assert.h"
#include "color.h"
Expand Down Expand Up @@ -842,6 +843,25 @@ bool achievements_tracker::is_hidden( const achievement *ach ) const
return false;
}

void achievements_tracker::write_json_achievements( std::ostream &achievement_file ) const
{
JsonOut jsout( achievement_file, true, 2 );
jsout.start_object();
jsout.member( "achievement_version", 0 );

std::vector<achievement_id> ach_ids;

for( const auto &kv : achievements_status_ ) {
if( kv.second.completion == achievement_completion::completed ) {
ach_ids.push_back( kv.first );
}
}

jsout.member( "achievements", ach_ids );

jsout.end_object();
}

std::string achievements_tracker::ui_text_for( const achievement *ach ) const
{
auto state_it = achievements_status_.find( ach->id );
Expand Down
2 changes: 2 additions & 0 deletions src/achievement.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ class achievements_tracker : public event_subscriber

void report_achievement( const achievement *, achievement_completion );

void write_json_achievements( std::ostream &achievement_file ) const;

achievement_completion is_completed( const achievement_id & ) const;
bool is_hidden( const achievement * ) const;
std::string ui_text_for( const achievement * ) const;
Expand Down
2 changes: 1 addition & 1 deletion src/debug_menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3336,7 +3336,7 @@ void debug()
break;
case debug_menu_index::UNLOCK_ALL:
if( query_yn(
_( "Activating this will add the Arcade Mode achievement unlocking all starting scenarios and professions for all worlds. The character who performs this action will need to die for it to be recorded. Achievements are tracked from the memorial folder if you need to get rid of this. Activating this will spoil factions and situations you may otherwise stumble upon naturally while playing. Some scenarios are frustrating for the uninitiated, and some professions skip portions of the game's content. If new to the game progression would otherwise help you be introduced to mechanics at a reasonable pace." ) ) ) {
_( "Activating this will add the Arcade Mode achievement unlocking all starting scenarios and professions for all worlds. You will need to save the character in order to record this. Achievements are tracked from the save/achievements/ folder if you need to get rid of this. Activating this will spoil factions and situations you may otherwise stumble upon naturally while playing. Some scenarios are frustrating for the uninitiated, and some professions skip portions of the game's content. If new to the game progression would otherwise help you be introduced to mechanics at a reasonable pace." ) ) ) {
get_achievements().report_achievement( &achievement_achievement_arcade_mode.obj(),
achievement_completion::completed );
}
Expand Down
3 changes: 3 additions & 0 deletions src/do_turn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ bool cleanup_at_end()
// and the overmap, and the local map.
g->save_maps(); //Omap also contains the npcs who need to be saved.

//save achievements entry
g->save_achievements();

g->death_screen();
std::chrono::seconds time_since_load =
std::chrono::duration_cast<std::chrono::seconds>(
Expand Down
54 changes: 54 additions & 0 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
#include "gates.h"
#include "get_version.h"
#include "harvest.h"
#include "help.h"
#include "iexamine.h"
#include "init.h"
#include "input.h"
Expand Down Expand Up @@ -143,6 +144,7 @@
#include "overmapbuffer.h"
#include "panels.h"
#include "past_games_info.h"
#include "past_achievements_info.h"
#include "path_info.h"
#include "pathfinding.h"
#include "pickup.h"
Expand Down Expand Up @@ -3333,6 +3335,57 @@ bool game::save_player_data()
;
}


bool game::save_achievements()
{
const std::string &achievement_dir = PATH_INFO::achievementdir();

//Check if achievement dir exists
if( !assure_dir_exist( achievement_dir ) ) {
dbg( D_ERROR ) << "game:save_achievements: Unable to make achievement directory.";
debugmsg( "Could not make '%s' directory", achievement_dir );
return false;
}

// This sets the maximum length for the filename
constexpr size_t suffix_len = 24 + 1;
constexpr size_t max_name_len = FILENAME_MAX - suffix_len;

const size_t name_len = u.name.size();
// Here -1 leaves space for the ~
const size_t truncated_name_len = ( name_len >= max_name_len ) ? ( max_name_len - 1 ) : name_len;

std::ostringstream achievement_file_path;

achievement_file_path << achievement_dir;

if( get_options().has_option( "ENCODING_CONV" ) && !get_option<bool>( "ENCODING_CONV" ) ) {
// Use the default locale to replace non-printable characters with _ in the player name.
std::locale locale{ "C" };
std::replace_copy_if( std::begin( u.name ), std::begin( u.name ) + truncated_name_len,
std::ostream_iterator<char>( achievement_file_path ),
[&]( const char c ) {
return !std::isgraph( c, locale );
}, '_' );
} else {
achievement_file_path << u.name;
}

// Add a ~ if the player name was actually truncated.
achievement_file_path << ( ( truncated_name_len != name_len ) ? "~-" : "-" );
const int character_id = get_player_character().getID().get_value();
const std::string json_path_string = achievement_file_path.str() + std::to_string(
character_id ) + ".json";

// Clear past achievements so that it will be reloaded
clear_past_achievements();

return write_to_file( json_path_string, [&]( std::ostream & fout ) {
get_achievements().write_json_achievements( fout );
}, _( "player achievements" ) );

}

event_bus &game::events()
{
return *event_bus_ptr;
Expand Down Expand Up @@ -3392,6 +3445,7 @@ bool game::save()
events().send<event_type::game_save>( time_since_load, total_time_played );
try {
if( !save_player_data() ||
!save_achievements() ||
!save_factions_missions_npcs() ||
!save_maps() ||
!get_auto_pickup().save_character() ||
Expand Down
1 change: 1 addition & 0 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,7 @@ class game

void move_save_to_graveyard();
bool save_player_data();
bool save_achievements();
// ########################## DATA ################################
// May be a bit hacky, but it's probably better than the header spaghetti
pimpl<map> map_ptr; // NOLINT(cata-serialize)
Expand Down
81 changes: 81 additions & 0 deletions src/past_achievements_info.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <vector>

#include "past_achievements_info.h"
#include "past_games_info.h"
#include "achievement.h"
#include "json.h"
#include "json_loader.h"

void past_achievements_info::clear()
{
*this = past_achievements_info();
}

bool past_achievements_info::is_completed( const achievement_id &ach ) const
{
auto ach_it = completed_achievements_.find( ach );
return ach_it != completed_achievements_.end();
}

/*
* This is for when the player copies their memorial folder
* in and expects those achievements to still work for scenarios/professions
*
* It will create a save/achievements/ entry called memorial_achievements.json,
* this file contains the achievement ids from the players memorials.
*/
bool past_achievements_info::migrate_memorial()
{
const std::string &oldachievements_file = PATH_INFO::oldachievements();
if( memorial_loaded_ ) {
return false;
}
memorial_loaded_ = true;

std::ostringstream oldachievements_file_path;
oldachievements_file_path << oldachievements_file;

return write_to_file( oldachievements_file, [&]( std::ostream & fout ) {
get_past_games().write_json_achievements( fout );
}, _( "player achievements" ) );
}

void past_achievements_info::load()
{
if( loaded_ ) {
return;
}
loaded_ = true;
const cata_path &achievement_dir = PATH_INFO::achievementdir_path();
assure_dir_exist( achievement_dir );

migrate_memorial();
std::vector<cata_path> filenames = get_files_from_path( ".json", achievement_dir, true, true );

for( const cata_path &filename : filenames ) {
int version;
std::vector<achievement_id> achievements;
JsonValue jsin = json_loader::from_path( filename );
JsonObject jo = jsin.get_object();
jo.read( "achievement_version", version );
if( version != 0 ) {
continue;
}
jo.read( "achievements", achievements );
completed_achievements_.insert( achievements.cbegin(), achievements.cend() );
}
}

static past_achievements_info past_achievements;
past_achievements_info::past_achievements_info() = default;

const past_achievements_info &get_past_achievements()
{
past_achievements.load();
return past_achievements;
}

void clear_past_achievements()
{
past_achievements.clear();
}
29 changes: 29 additions & 0 deletions src/past_achievements_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once
#ifndef CATA_SRC_PAST_ACHIEVEMENTS_INFO_H
#define CATA_SRC_PAST_ACHIEVEMENTS_INFO_H

#include <set>

#include "achievement.h"

class past_achievements_info
{
public:
past_achievements_info();

bool migrate_memorial();
void load();
void clear();
bool is_completed( const achievement_id & ) const;
private:

bool loaded_ = false;
bool memorial_loaded_ = false;
std::set<achievement_id> completed_achievements_;
};


const past_achievements_info &get_past_achievements();
void clear_past_achievements();

#endif // CATA_SRC_PAST_ACHIEVEMENTS_INFO_H
22 changes: 22 additions & 0 deletions src/past_games_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <map>
#include <stdexcept>
#include <string>
#include <vector>
#include <unordered_set>
#include <utility>

#include "achievement.h"
Expand Down Expand Up @@ -90,6 +92,25 @@ const achievement_completion_info *past_games_info::achievement( const achieveme
return ach_it == completed_achievements_.end() ? nullptr : &ach_it->second;
}

void past_games_info::write_json_achievements( std::ostream &achievement_file ) const
{
JsonOut jsout( achievement_file, true, 2 );
jsout.start_object();
jsout.member( "achievement_version", 0 );

std::unordered_set<std::string> ach_id_strings;

for( achievement_id kv : ach_ids_ ) {
if( achievement( kv ) ) {
ach_id_strings.insert( kv.c_str() );
}
}

jsout.member( "achievements", ach_id_strings );

jsout.end_object();
}

void past_games_info::ensure_loaded()
{
if( loaded_ ) {
Expand Down Expand Up @@ -165,6 +186,7 @@ void past_games_info::ensure_loaded()
continue;
}
achievement_id ach = ach_it->second.get<achievement_id>();
ach_ids_.push_back( ach );
completed_achievements_[ach].games_completed.push_back( &game );
}
inp_mngr.pump_events();
Expand Down
2 changes: 2 additions & 0 deletions src/past_games_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class past_games_info
public:
past_games_info();

void write_json_achievements( std::ostream &achievement_file ) const;
void ensure_loaded();
void clear();
const achievement_completion_info *achievement( const achievement_id & ) const;
Expand All @@ -56,6 +57,7 @@ class past_games_info
bool loaded_ = false;
std::unordered_map<achievement_id, achievement_completion_info> completed_achievements_;
std::vector<past_game_info> info_;
std::vector<achievement_id> ach_ids_;
};

const past_games_info &get_past_games();
Expand Down
24 changes: 24 additions & 0 deletions src/path_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ static std::string autonote_value;
static std::string keymap_value;
static std::string options_value;
static std::string memorialdir_value;
static std::string achievementdir_value;
static std::string oldachievements_value;
static std::string langdir_value;

static cata_path autonote_path_value;
Expand All @@ -53,6 +55,8 @@ static cata_path gfxdir_path_value;
static cata_path keymap_path_value;
static cata_path langdir_path_value;
static cata_path memorialdir_path_value;
static cata_path achievementdir_path_value;
static cata_path oldachievements_path_value;
static cata_path motd_path_value;
static cata_path options_path_value;
static cata_path savedir_path_value;
Expand Down Expand Up @@ -142,6 +146,10 @@ void PATH_INFO::set_standard_filenames()
savedir_path_value = cata_path{ cata_path::root_path::save, fs::path{} };
memorialdir_value = user_dir_value + "memorial/";
memorialdir_path_value = user_dir_path_value / "memorial";
achievementdir_value = savedir_value + "achievement/";
achievementdir_path_value = savedir_path_value / "achievement";
oldachievements_value = achievementdir_value + "memorial_achievements.json";
oldachievements_path_value = achievementdir_path_value / "memorial_achievements.json";

#if defined(USE_XDG_DIR)
const char *user_dir;
Expand Down Expand Up @@ -327,10 +335,26 @@ std::string PATH_INFO::memorialdir()
{
return memorialdir_value;
}
std::string PATH_INFO::achievementdir()
{
return achievementdir_value;
}
std::string PATH_INFO::oldachievements()
{
return oldachievements_value;
}
cata_path PATH_INFO::memorialdir_path()
{
return memorialdir_path_value;
}
cata_path PATH_INFO::achievementdir_path()
{
return achievementdir_path_value;
}
cata_path PATH_INFO::oldachievements_path()
{
return oldachievements_path_value;
}
cata_path PATH_INFO::jsondir()
{
return datadir_path_value / "core";
Expand Down
4 changes: 4 additions & 0 deletions src/path_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ std::string user_font();
std::string graveyarddir();
std::string keymap();
std::string memorialdir();
std::string achievementdir();
std::string oldachievements();
std::string player_base_save_path();
std::string savedir();
std::string sokoban();
Expand Down Expand Up @@ -75,6 +77,8 @@ cata_path langdir_path();
cata_path lastworld();
cata_path legacy_fontdata();
cata_path memorialdir_path();
cata_path achievementdir_path();
cata_path oldachievements_path();
cata_path moddir();
cata_path mods_dev_default();
cata_path mods_user_default();
Expand Down
Loading

0 comments on commit 622f089

Please sign in to comment.