Skip to content

Commit

Permalink
Makes achievements_score query database for count (#79597)
Browse files Browse the repository at this point in the history
Instead of `/datum/award/score/achievements_score` counting achievement
datums in-game and trying to keep up with what the database has we now
just query the database for its current count of unlocked achievements
by overriding the procs the achievements panel builds data from. This
avoids cases like #79555.

Count is still loaded to achievement data datum so it can be saved at
round end.

@Time-Green
  • Loading branch information
Jordie0608 authored and Gboster-0 committed Nov 23, 2023
1 parent cc5476f commit 6691961
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 32 deletions.
5 changes: 0 additions & 5 deletions code/__DEFINES/achievements.dm
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@
#define ACHIEVEMENT_DEFAULT "default"
#define ACHIEVEMENT_SCORE "score"

///the priority for which awards are orded on [/datum/achievement_data/load_all_achievements()]
#define AWARD_PRIORITY_DEFAULT 100
///the priority of the achievements score. NO achievement should have a priority equal or lower than this.
#define AWARD_PRIORITY_LAST 0

/// preferences for the sound played when unlocking an achievement
#define CHEEVO_SOUND_TADA "Tada Fanfare"
#define CHEEVO_SOUND_JINGLE "Beeps Jingle"
Expand Down
6 changes: 0 additions & 6 deletions code/__HELPERS/cmp.dm
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,6 @@
/proc/cmp_heretic_knowledge(datum/heretic_knowledge/knowledge_a, datum/heretic_knowledge/knowledge_b)
return initial(knowledge_b.priority) - initial(knowledge_a.priority)

/// Used by /datum/achievement_data/load_all_achievements() to determine in which order awards have to be loaded.
/proc/cmp_award_priority(type_a, type_b)
var/datum/award/award_a = SSachievements.awards[type_a]
var/datum/award/award_b = SSachievements.awards[type_b]
return award_b?.load_priority - award_a?.load_priority

/// Passed a list of assoc lists, sorts them by the list's "name" keys.
/proc/cmp_assoc_list_name(list/A, list/B)
return sorttext(B["name"], A["name"])
Expand Down
4 changes: 2 additions & 2 deletions code/datums/achievements/_achievement_data.dm
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
kv[key] = value
qdel(Query)

for(var/award_type in sortTim(subtypesof(/datum/award), GLOBAL_PROC_REF(cmp_award_priority)))
for(var/award_type in subtypesof(/datum/award))
var/datum/award/award = SSachievements.awards[award_type]
if(!award || !award.name) //Skip abstract achievements types
continue
Expand Down Expand Up @@ -116,7 +116,7 @@
"icon_class" = assets.icon_class_name(award.icon),
"value" = data[achievement_type],
)
award_data += award.get_ui_data()
award_data += award.get_ui_data(user.ckey)
.["achievements"] += list(award_data)

for(var/score in SSachievements.scores)
Expand Down
59 changes: 40 additions & 19 deletions code/datums/achievements/_awards.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
//Value returned on db connection failure, in case we want to differ 0 and nonexistent later on
var/default_value = FALSE

///Whether the award has to be loaded before or after other awards on [/datum/achievement_data/load_all_achievements()]
var/load_priority = AWARD_PRIORITY_DEFAULT

///This proc loads the achievement data from the hub.
/datum/award/proc/load(key)
if(!SSdbcore.Connect())
Expand Down Expand Up @@ -109,7 +106,6 @@
/datum/award/achievement/on_unlock(mob/user)
. = ..()
to_chat(user, span_greenannounce("<B>Achievement unlocked: [name]!</B>"))
user.client.give_award(/datum/award/score/achievements_score, user, 1)
var/sound/sound_to_send = LAZYACCESS(GLOB.achievement_sounds, user.client.prefs.read_preference(/datum/preference/choiced/sound_achievement))
if(sound_to_send)
SEND_SOUND(user, sound_to_send)
Expand Down Expand Up @@ -177,20 +173,45 @@
desc = "Don't worry, metagaming is all that matters."
icon = "elephant" //Obey the reference
database_id = ACHIEVEMENTS_SCORE
load_priority = AWARD_PRIORITY_LAST //See below

/**
* If the raw value is not numerical, it's likely this is the first time the score is being loaded for a ckey.
* So, let's start counting how many achievements have been unlocked so far and return its value instead,
* which is why this award should always be loaded last.
*/
/datum/award/score/achievements_score/get_ui_data(key)
. = ..()
var/datum/db_query/get_unlocked_count = SSdbcore.NewQuery(
"SELECT COUNT(m.achievement_key) FROM [format_table_name("achievements")] AS a JOIN [format_table_name("achievement_metadata")] m ON a.achievement_key = m.achievement_key AND m.achievement_type = 'Achievement' WHERE a.ckey = :ckey",
list("ckey" = key)
)
if(!get_unlocked_count.Execute(async = TRUE))
qdel(get_unlocked_count)
.["value"] = default_value
return .
if(get_unlocked_count.NextRow())
.["value"] = text2num(get_unlocked_count.item[1])
qdel(get_unlocked_count)
return .

/datum/award/score/achievements_score/LoadHighScores()
var/datum/db_query/get_unlocked_highscore = SSdbcore.NewQuery(
"SELECT ckey, COUNT(ckey) AS c FROM [format_table_name("achievements")] AS a JOIN [format_table_name("achievement_metadata")] m ON a.achievement_key = m.achievement_key AND m.achievement_type = 'Achievement' GROUP BY ckey ORDER BY c DESC LIMIT 50",
)
if(!get_unlocked_highscore.Execute(async = TRUE))
qdel(get_unlocked_highscore)
return
else
while(get_unlocked_highscore.NextRow())
var/key = get_unlocked_highscore.item[1]
var/score = text2num(get_unlocked_highscore.item[2])
high_scores[key] = score
qdel(get_unlocked_highscore)

/datum/award/score/achievements_score/on_achievement_data_init(datum/achievement_data/holder, database_value)
if(isnum(database_value))
return ..()
//We need to keep the value differents so that it's properly saved at the end of the round.
holder.original_cached_data[type] = 0
var/value = 0
for(var/award_type in holder.data)
if(ispath(award_type, /datum/award/achievement) && holder.data[award_type])
value++
holder.data[type] = value
var/datum/db_query/get_unlocked_load = SSdbcore.NewQuery(
"SELECT COUNT(m.achievement_key) FROM [format_table_name("achievements")] AS a JOIN [format_table_name("achievement_metadata")] m ON a.achievement_key = m.achievement_key AND m.achievement_type = 'Achievement' WHERE a.ckey = :ckey",
list("ckey" = holder.owner_ckey)
)
if(!get_unlocked_load.Execute(async = TRUE))
qdel(get_unlocked_load)
return
if(get_unlocked_load.NextRow())
holder.data[type] = text2num(get_unlocked_load.item[1]) || 0
holder.original_cached_data[type] = 0
qdel(get_unlocked_load)

0 comments on commit 6691961

Please sign in to comment.