Skip to content

Commit

Permalink
Merge pull request #40090 from jbytheway/hidden_by_achievements
Browse files Browse the repository at this point in the history
Support for achievements hidden by other achievements
  • Loading branch information
ifreund authored May 3, 2020
2 parents a4d2de0 + 007e7ce commit 0c074f2
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 3 deletions.
50 changes: 50 additions & 0 deletions data/json/achievements.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,63 @@
"time_constraint": { "since": "game_start", "is": "<=", "target": "1 minute" },
"requirements": [ { "event_statistic": "num_avatar_kills", "is": ">=", "target": 1 } ]
},
{
"id": "achievement_kill_10_monsters",
"type": "achievement",
"name": "Decamate",
"requirements": [ { "event_statistic": "num_avatar_kills", "is": ">=", "target": 10 } ]
},
{
"id": "achievement_kill_100_monsters",
"type": "achievement",
"name": "Centinel",
"hidden_by": [ "achievement_kill_10_monsters" ],
"requirements": [ { "event_statistic": "num_avatar_kills", "is": ">=", "target": 100 } ]
},
{
"id": "achievement_survive_one_day",
"type": "achievement",
"name": "The first day of the rest of their unlives",
"description": "Survive for a day and find a safe place to sleep",
"time_constraint": { "since": "game_start", "is": ">=", "target": "1 day" },
"requirements": [ { "event_statistic": "num_avatar_wake_ups", "is": "anything" } ]
},
{
"id": "achievement_survive_7_days",
"type": "achievement",
"name": "Thank God it's Friday",
"description": "Survive for a week",
"hidden_by": [ "achievement_survive_one_day" ],
"time_constraint": { "since": "game_start", "is": ">=", "target": "7 days" },
"requirements": [ { "event_statistic": "num_avatar_wake_ups", "is": "anything" } ]
},
{
"id": "achievement_survive_28_days",
"type": "achievement",
"name": "28 days later",
"description": "Survive for a month",
"hidden_by": [ "achievement_survive_7_days" ],
"time_constraint": { "since": "game_start", "is": ">=", "target": "28 days" },
"requirements": [ { "event_statistic": "num_avatar_wake_ups", "is": "anything" } ]
},
{
"id": "achievement_survive_91_days",
"type": "achievement",
"name": "A time to every purpose under heaven",
"description": "Survive for a season",
"hidden_by": [ "achievement_survive_28_days" ],
"time_constraint": { "since": "game_start", "is": ">=", "target": "91 days" },
"requirements": [ { "event_statistic": "num_avatar_wake_ups", "is": "anything" } ]
},
{
"id": "achievement_survive_365_days",
"type": "achievement",
"name": "Brighter days ahead?",
"description": "Survive for a year",
"hidden_by": [ "achievement_survive_91_days" ],
"time_constraint": { "since": "game_start", "is": ">=", "target": "365 days" },
"requirements": [ { "event_statistic": "num_avatar_wake_ups", "is": "anything" } ]
},
{
"id": "achievement_marathon",
"type": "achievement",
Expand Down
12 changes: 11 additions & 1 deletion doc/JSON_INFO.md
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,17 @@ an `event_statistic`. For example:
The `"is"` field must be `">="`, `"<="` or `"anything"`. When it is not
`"anything"` the `"target"` must be present, and must be an integer.

Another optional field is
There are further optional fields:

```C++
"hidden_by": [ "other_achievement_id" ]
```

Give a list of other achievement ids. This achievement will be hidden (i.e.
not appear in the achievements UI) until all of the achievements listed have
been completed.

Use this to prevent spoilers or to reduce clutter in the list of achievements.

```C++
"time_constraint": { "since": "game_start", "is": "<=", "target": "1 minute" }
Expand Down
32 changes: 30 additions & 2 deletions src/achievement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ const achievement &string_id<achievement>::obj() const
return achievement_factory.obj( *this );
}

/** @relates string_id */
template<>
bool string_id<achievement>::is_valid() const
{
return achievement_factory.is_valid( *this );
}

namespace io
{

Expand Down Expand Up @@ -310,18 +317,25 @@ void achievement::load( const JsonObject &jo, const std::string & )
{
mandatory( jo, was_loaded, "name", name_ );
optional( jo, was_loaded, "description", description_ );
optional( jo, was_loaded, "hidden_by", hidden_by_ );
optional( jo, was_loaded, "time_constraint", time_constraint_ );
mandatory( jo, was_loaded, "requirements", requirements_ );
}

void achievement::check() const
{
for( const achievement_requirement &req : requirements_ ) {
req.check( id );
for( const string_id<achievement> &a : hidden_by_ ) {
if( !a.is_valid() ) {
debugmsg( "Achievement %s specifies hidden_by achievement %s, but the latter does not "
"exist.", id.str(), a.str() );
}
}
if( time_constraint_ ) {
time_constraint_->check( id );
}
for( const achievement_requirement &req : requirements_ ) {
req.check( id );
}
}

static std::string text_for_requirement( const achievement_requirement &req,
Expand Down Expand Up @@ -599,6 +613,20 @@ achievement_completion achievements_tracker::is_completed( const string_id<achie
return it->second.completion;
}

bool achievements_tracker::is_hidden( const achievement *ach ) const
{
if( is_completed( ach->id ) == achievement_completion::completed ) {
return false;
}

for( const string_id<achievement> &hidden_by : ach->hidden_by() ) {
if( is_completed( hidden_by ) != achievement_completion::completed ) {
return true;
}
}
return false;
}

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

const std::vector<string_id<achievement>> &hidden_by() const {
return hidden_by_;
}

class time_bound
{
public:
Expand Down Expand Up @@ -98,6 +102,7 @@ class achievement
private:
translation name_;
translation description_;
std::vector<string_id<achievement>> hidden_by_;
cata::optional<time_bound> time_constraint_;
std::vector<achievement_requirement> requirements_;
};
Expand Down Expand Up @@ -170,6 +175,7 @@ class achievements_tracker : public event_subscriber
void report_achievement( const achievement *, achievement_completion );

achievement_completion is_completed( const string_id<achievement> & ) const;
bool is_hidden( const achievement * ) const;
std::string ui_text_for( const achievement * ) const;

void clear();
Expand Down
5 changes: 5 additions & 0 deletions src/scores_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ static std::string get_achievements_text( const achievements_tracker &achievemen
{
std::string os;
std::vector<const achievement *> valid_achievements = achievements.valid_achievements();
valid_achievements.erase(
std::remove_if( valid_achievements.begin(), valid_achievements.end(),
[&]( const achievement * a ) {
return achievements.is_hidden( a );
} ), valid_achievements.end() );
using sortable_achievement =
std::tuple<achievement_completion, std::string, const achievement *>;
std::vector<sortable_achievement> sortable_achievements;
Expand Down
20 changes: 20 additions & 0 deletions tests/stats_tracker_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,26 @@ TEST_CASE( "achievments_tracker", "[stats]" )
CHECK( achievements_completed.count( a_survive_one_day ) );
}

SECTION( "hidden_kills" ) {
const character_id u_id = g->u.getID();
const mtype_id mon_zombie( "mon_zombie" );
const cata::event avatar_zombie_kill =
cata::event::make<event_type::character_kills_monster>( u_id, mon_zombie );

string_id<achievement> a_kill_10( "achievement_kill_10_monsters" );
string_id<achievement> a_kill_100( "achievement_kill_100_monsters" );

b.send<event_type::game_start>( u_id );

CHECK( !a.is_hidden( &*a_kill_10 ) );
CHECK( a.is_hidden( &*a_kill_100 ) );
for( int i = 0; i < 10; ++i ) {
b.send( avatar_zombie_kill );
}
CHECK( !a.is_hidden( &*a_kill_10 ) );
CHECK( !a.is_hidden( &*a_kill_100 ) );
}

SECTION( "kills" ) {
time_duration time_since_game_start = GENERATE( 30_seconds, 10_minutes );
CAPTURE( time_since_game_start );
Expand Down

0 comments on commit 0c074f2

Please sign in to comment.