Files
Alexis 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

137 lines
4.8 KiB
Plaintext

SUBSYSTEM_DEF(achievements)
name = "Achievements"
ss_flags = SS_NO_FIRE
var/achievements_enabled = FALSE
///List of achievements
var/list/datum/award/achievement/achievements = list()
///The achievement with the highest amount of players that have unlocked it.
var/datum/award/achievement/most_unlocked_achievement
///List of scores
var/list/datum/award/score/scores = list()
///List of all awards
var/list/datum/award/awards = list()
/datum/controller/subsystem/achievements/Initialize()
if(!SSdbcore.Connect())
return SS_INIT_NO_NEED
achievements_enabled = TRUE
var/list/achievements_by_db_id = list()
for(var/datum/award/achievement/achievement as anything in subtypesof(/datum/award/achievement))
if(!initial(achievement.database_id)) // abstract type
continue
var/datum/award/achievement/instance = new achievement
achievements[achievement] = instance
awards[achievement] = instance
achievements_by_db_id[instance.database_id] = instance
for(var/datum/award/score/score as anything in subtypesof(/datum/award/score))
if(!initial(score.database_id)) // abstract type
continue
var/instance = new score
scores[score] = instance
awards[score] = instance
update_metadata()
/**
* Count how many (unlocked) achievements are in the achievements database
* then store that amount in the times_achieved variable for each achievement.
*
* Thanks to Jordie for the query.
*/
var/datum/db_query/query = SSdbcore.NewQuery(
"SELECT a.achievement_key, COUNT(a.achievement_key) AS count FROM achievements a \
JOIN achievement_metadata m ON a.achievement_key = m.achievement_key AND m.achievement_type = 'achievement' \
GROUP BY a.achievement_key ORDER BY count DESC"
)
if(query.Execute(async = TRUE))
while(query.NextRow())
var/id = query.item[1]
var/datum/award/achievement/instance = id ? achievements_by_db_id[id] : null
if(isnull(instance)) // removed achievement
continue
instance.times_achieved = query.item[2]
// the results are ordered in descending orders, so the first in the list should be the most unlocked one.
if(!most_unlocked_achievement)
most_unlocked_achievement = instance
qdel(query)
for(var/client/C in GLOB.clients)
if(!C.persistent_client.achievements.initialized)
C.persistent_client.achievements.InitializeData()
return SS_INIT_SUCCESS
/datum/controller/subsystem/achievements/Shutdown()
save_achievements_to_db()
/datum/controller/subsystem/achievements/proc/save_achievements_to_db()
var/list/cheevos_to_save = list()
for(var/ckey in GLOB.persistent_clients_by_ckey)
var/datum/persistent_client/PD = GLOB.persistent_clients_by_ckey[ckey]
if(!PD || !PD.achievements)
continue
cheevos_to_save += PD.achievements.get_changed_data()
if(!length(cheevos_to_save))
return
SSdbcore.MassInsert(format_table_name("achievements"), cheevos_to_save, duplicate_key = TRUE)
SEND_SIGNAL(src, COMSIG_ACHIEVEMENTS_SAVED_TO_DB)
//Update the metadata if any are behind
/datum/controller/subsystem/achievements/proc/update_metadata()
var/list/current_metadata = list()
//select metadata here
var/datum/db_query/Q = SSdbcore.NewQuery("SELECT achievement_key,achievement_version FROM [format_table_name("achievement_metadata")]")
if(!Q.Execute(async = TRUE))
qdel(Q)
return
else
while(Q.NextRow())
current_metadata[Q.item[1]] = text2num(Q.item[2])
qdel(Q)
var/list/to_update = list()
for(var/T in awards)
var/datum/award/A = awards[T]
if(!current_metadata[A.database_id] || current_metadata[A.database_id] < A.achievement_version)
to_update += list(A.get_metadata_row())
if(to_update.len)
SSdbcore.MassInsert(format_table_name("achievement_metadata"),to_update,duplicate_key = TRUE)
var/list/orphaned_keys = get_orphaned_keys(FALSE)
if(orphaned_keys.len)
message_admins("Achievement metadata found without matching achievement, use Achievements-Admin-Panel verb to cleanup if necessary")
/// returns list of metadata keys and versions in db with no matching achievement datum, either deleted achievements, or from server with code ahead of us.
/datum/controller/subsystem/achievements/proc/get_orphaned_keys(include_archived = TRUE)
. = list()
var/list/current_metadata = list()
// Fetch all keys from the db
var/datum/db_query/Q = SSdbcore.NewQuery("SELECT achievement_key,achievement_version FROM [format_table_name("achievement_metadata")]")
if(!Q.Execute(async = TRUE))
qdel(Q)
return
else
while(Q.NextRow())
current_metadata[Q.item[1]] = Q.item[2]
qdel(Q)
var/list/achievements_by_db_id = list()
for(var/datum/award/award as anything in subtypesof(/datum/award))
if(!initial(award.database_id)) // abstract type
continue
achievements_by_db_id[award.database_id] = TRUE
for(var/key in current_metadata)
if(achievements_by_db_id[key])
continue
if(!include_archived && current_metadata[key] == ACHIEVEMENT_ARCHIVED_VERSION)
continue
.[key] = current_metadata[key]