Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for achievements hidden by other achievements #40090

Merged
merged 4 commits into from
May 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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