Skip to content

Commit

Permalink
Merge pull request #545 from Gboster-0/achievements
Browse files Browse the repository at this point in the history
Moar achievements
  • Loading branch information
dwasint authored Nov 29, 2023
2 parents beafea8 + 6691961 commit 2aae3bb
Show file tree
Hide file tree
Showing 48 changed files with 512 additions and 168 deletions.
12 changes: 12 additions & 0 deletions code/__DEFINES/achievements.dm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
#define ACHIEVEMENT_DEFAULT "default"
#define ACHIEVEMENT_SCORE "score"

/// preferences for the sound played when unlocking an achievement
#define CHEEVO_SOUND_TADA "Tada Fanfare"
#define CHEEVO_SOUND_JINGLE "Beeps Jingle"
#define CHEEVO_SOUND_PING "Success Ping"
#define CHEEVO_SOUND_OFF "Disabled"

//Misc Medal hub IDs
#define MEDAL_METEOR "Your Life Before Your Eyes"
#define MEDAL_PULSE "Jackpot"
Expand Down Expand Up @@ -46,6 +52,9 @@
#define MEDAL_NARSUPREME "Narsupreme"
#define MEDAL_SPRINGLOCK "The Man Inside the Modsuit"
#define MEDAL_HEALTHY "Heart Healthy"
#define MEDAL_DEBT_EXTINGUISHED "Debt Extinguished"
#define MEDAL_ARCHMAGE "Archmage"
#define MEDAL_THEORETICAL_LIMITS "All Within Theoretical Limits"

//Skill medal hub IDs
#define MEDAL_LEGENDARY_MINER "Legendary Miner"
Expand Down Expand Up @@ -127,6 +136,9 @@
// DB ID for style point count
#define STYLE_SCORE "Style Score"

/// DB ID for the amount of achievements unlocked by the player.
#define ACHIEVEMENTS_SCORE "Achievements Score"

// Tourist related achievements and scores

//centcom grades (achievement)
Expand Down
3 changes: 3 additions & 0 deletions code/__DEFINES/dcs/signals/signals_datum.dm
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@
#define COMSIG_MODULAR_COMPUTER_FILE_DELETING "comsig_modular_computer_file_deleting"
/// From /obj/item/modular_computer/proc/store_file: ()
#define COMSIG_MODULAR_COMPUTER_FILE_DELETED "comsig_modular_computer_file_deleted"

///from /datum/bank_account/pay_debt(), after a portion or all the debt has been paid.
#define COMSIG_BANK_ACCOUNT_DEBT_PAID "bank_account_debt_paid"
3 changes: 2 additions & 1 deletion code/__DEFINES/economy.dm
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

#define STATION_TARGET_BUFFER 25


///The coefficient for the amount of dosh that's collected everytime some is earned or received.
#define DEBT_COLLECTION_COEFF 0.75

#define MAX_GRANT_DPT 500

Expand Down
2 changes: 2 additions & 0 deletions code/__DEFINES/quirks.dm
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@
#define QUIRK_CHANGES_APPEARANCE (1<<2)
/// The only thing this quirk effects is mood so it should be disabled if mood is
#define QUIRK_MOODLET_BASED (1<<3)
/// This quirk shouldn't be shown by health analyzers and hud, perhaps as considering it medical condition is a far stretch.
#define QUIRK_HIDE_FROM_SCAN (1<<4)
8 changes: 8 additions & 0 deletions code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,14 @@
/// Game has round finished
#define GAME_STATE_FINISHED 4

// Used for SSticker.force_ending
/// Default, round is not being forced to end.
#define END_ROUND_AS_NORMAL 0
/// End the round now as normal
#define FORCE_END_ROUND 1
/// For admin forcing roundend, can be used to distinguish the two
#define ADMIN_FORCE_END_ROUND 2

/**
Create a new timer and add it to the queue.
* Arguments:
Expand Down
3 changes: 3 additions & 0 deletions code/__DEFINES/time.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
///displays the current time into the round, with a lot of extra code just there for ensuring it looks okay after an entire day passes
#define ROUND_TIME(...) ( "[world.time - SSticker.round_start_time > MIDNIGHT_ROLLOVER ? "[round((world.time - SSticker.round_start_time)/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]" )

///Returns the time that has passed since the game started
#define STATION_TIME_PASSED(...) (world.time - SSticker.round_start_time)

/// Define that just has the current in-universe year for use in whatever context you might want to display that in. (For example, 2022 -> 2562 given a 540 year offset)
#define CURRENT_STATION_YEAR (GLOB.year_integer + STATION_YEAR_OFFSET)

Expand Down
36 changes: 30 additions & 6 deletions code/__HELPERS/roundend.dm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#define PERSONAL_LAST_ROUND "personal last round"
#define SERVER_LAST_ROUND "server last round"

GLOBAL_LIST_INIT(round_end_images, world.file2list("data/image_urls.txt"))
GLOBAL_LIST_INIT(achievements_unlocked, list())

GLOBAL_LIST_INIT(round_end_images, world.file2list("data/image_urls.txt")) // MONKEYSTATION EDIT ADDITION PR #11 - update roundend.dm

/datum/controller/subsystem/ticker/proc/gather_roundend_feedback()
gather_antag_data()
Expand Down Expand Up @@ -203,22 +205,20 @@ GLOBAL_LIST_INIT(round_end_images, world.file2list("data/image_urls.txt"))
player_client.give_award(/datum/award/score/hardcore_random, human_mob, round(human_mob.hardcore_survival_score))


/datum/controller/subsystem/ticker/proc/declare_completion()
/datum/controller/subsystem/ticker/proc/declare_completion(was_forced = END_ROUND_AS_NORMAL)
set waitfor = FALSE

for(var/datum/callback/roundend_callbacks as anything in round_end_events)
roundend_callbacks.InvokeAsync()
LAZYCLEARLIST(round_end_events)

var/speed_round = FALSE
if(world.time - SSticker.round_start_time <= 300 SECONDS)
speed_round = TRUE
var/speed_round = (STATION_TIME_PASSED() <= 10 MINUTES)

for(var/client/C in GLOB.clients)
if(!C?.credits)
C?.RollCredits()
C?.playtitlemusic(40)
if(speed_round)
if(speed_round && was_forced != ADMIN_FORCE_END_ROUND)
C?.give_award(/datum/award/achievement/misc/speed_round, C?.mob)
HandleRandomHardcoreScore(C)

Expand Down Expand Up @@ -384,6 +384,8 @@ GLOBAL_LIST_INIT(round_end_images, world.file2list("data/image_urls.txt"))
parts += goal_report()
//Economy & Money
parts += market_report()
//Player Achievements
parts += cheevo_report()

list_clear_nulls(parts)

Expand Down Expand Up @@ -862,3 +864,25 @@ GLOBAL_LIST_INIT(round_end_images, world.file2list("data/image_urls.txt"))
return
qdel(query_update_everything_ranks)
qdel(query_check_everything_ranks)

/datum/controller/subsystem/ticker/proc/cheevo_report()
var/list/parts = list()
if(length(GLOB.achievements_unlocked))
parts += "<span class='header'>Achievement Get!</span><BR>"
parts += "<span class='infoplain'>Total Achievements Earned: <B>[length(GLOB.achievements_unlocked)]!</B></span><BR>"
parts += "<ul class='playerlist'>"
for(var/datum/achievement_report/cheevo_report in GLOB.achievements_unlocked)
parts += "<BR>[cheevo_report.winner_key] was <b>[cheevo_report.winner]</b>, who earned the [span_greentext("'[cheevo_report.cheevo]'")] achievement at [cheevo_report.award_location]!<BR>"
parts += "</ul>"
return "<div class='panel greenborder'><ul>[parts.Join()]</ul></div>"

///A datum containing the info necessary for an achievement readout, reported and added to the global list in /datum/award/achievement/on_unlock(mob/user)
/datum/achievement_report
///The winner of this achievement.
var/winner
///The achievement that was won.
var/cheevo
///The ckey of our winner
var/winner_key
///The name of the area we earned this cheevo in
var/award_location
8 changes: 8 additions & 0 deletions code/_globalvars/lists/achievements.dm
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
GLOBAL_LIST_EMPTY(commendations)
///A list of the current achievement categories supported by the UI and checked by the achievement unit test
GLOBAL_LIST_INIT(achievement_categories, list("Bosses", "Jobs", "Skills", "Misc", "Mafia", "Scores"))
///A list of sounds that can be played when unlocking an achievement, set in the preferences.
GLOBAL_LIST_INIT(achievement_sounds, list(
CHEEVO_SOUND_PING = sound('sound/effects/glockenspiel_ping.ogg', volume = 70),
CHEEVO_SOUND_JINGLE = sound('sound/effects/beeps_jingle.ogg', volume = 70),
CHEEVO_SOUND_TADA = sound('sound/effects/tada_fanfare.ogg', volume = 30),
))
49 changes: 39 additions & 10 deletions code/controllers/subsystem/achievements.dm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ SUBSYSTEM_DEF(achievements)

///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
Expand All @@ -16,18 +18,47 @@ SUBSYSTEM_DEF(achievements)
return SS_INIT_NO_NEED
achievements_enabled = TRUE

for(var/T in subtypesof(/datum/award/achievement))
var/instance = new T
achievements[T] = instance
awards[T] = instance
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/T in subtypesof(/datum/award/score))
var/instance = new T
scores[T] = instance
awards[T] = 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/i in GLOB.clients)
var/client/C = i
if(!C.player_details.achievements.initialized)
Expand Down Expand Up @@ -65,8 +96,6 @@ SUBSYSTEM_DEF(achievements)
var/list/to_update = list()
for(var/T in awards)
var/datum/award/A = awards[T]
if(!A.database_id)
continue
if(!current_metadata[A.database_id] || current_metadata[A.database_id] < A.achievement_version)
to_update += list(A.get_metadata_row())

Expand Down
8 changes: 5 additions & 3 deletions code/controllers/subsystem/ticker.dm
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ SUBSYSTEM_DEF(ticker)

/// state of current round (used by process()) Use the defines GAME_STATE_* !
var/current_state = GAME_STATE_STARTUP
/// Boolean to track if round was ended by admin intervention or a "round-ending" event, like summoning Nar'Sie, a blob victory, the nuke going off, etc.
var/force_ending = FALSE
/// Boolean to track if round should be forcibly ended next ticker tick.
/// Set by admin intervention ([ADMIN_FORCE_END_ROUND])
/// or a "round-ending" event, like summoning Nar'Sie, a blob victory, the nuke going off, etc. ([FORCE_END_ROUND])
var/force_ending = END_ROUND_AS_NORMAL
/// If TRUE, there is no lobby phase, the game starts immediately.
var/start_immediately = FALSE
/// Boolean to track and check if our subsystem setup is done.
Expand Down Expand Up @@ -212,7 +214,7 @@ SUBSYSTEM_DEF(ticker)
mode.process(wait * 0.1)
check_queue()

if(!roundend_check_paused && mode.check_finished(force_ending) || force_ending)
if(!roundend_check_paused && (mode.check_finished() || force_ending))
current_state = GAME_STATE_FINISHED
toggle_ooc(TRUE) // Turn it on
toggle_dooc(TRUE)
Expand Down
56 changes: 20 additions & 36 deletions code/datums/achievements/_achievement_data.dm
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,11 @@
kv[key] = value
qdel(Query)

for(var/T in subtypesof(/datum/award))
var/datum/award/A = SSachievements.awards[T]
if(!A || !A.name) //Skip abstract achievements types
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
if(!data[T])
data[T] = A.parse_value(kv[A.database_id])
original_cached_data[T] = data[T]
award.on_achievement_data_init(src, kv[award.database_id])

///Updates local cache with db data for the given achievement type if it wasn't loaded yet.
/datum/achievement_data/proc/get_data(achievement_type)
Expand All @@ -77,22 +75,12 @@
A.on_unlock(user) //Only on default achievement, as scores keep going up.
else if(istype(A, /datum/award/score))
data[achievement_type] += value
update_static_data(user)

///Getter for the status/score of an achievement
/datum/achievement_data/proc/get_achievement_status(achievement_type)
return data[achievement_type]

///Resets an achievement to default values.
/datum/achievement_data/proc/reset(achievement_type)
if(!SSachievements.achievements_enabled)
return
var/datum/award/A = SSachievements.awards[achievement_type]
get_data(achievement_type)
if(istype(A, /datum/award/achievement))
data[achievement_type] = FALSE
else if(istype(A, /datum/award/score))
data[achievement_type] = 0

/datum/achievement_data/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/simple/achievements),
Expand All @@ -107,34 +95,30 @@
ui = new(user, src, "Achievements")
ui.open()

/datum/achievement_data/ui_data(mob/user)
var/ret_data = list() // screw standards (qustinnus you must rename src.data ok)
ret_data["categories"] = list("Bosses", "Jobs", "Misc", "Mafia", "Scores")
ret_data["achievements"] = list()
ret_data["user_key"] = user.ckey
/datum/achievement_data/ui_static_data(mob/user)
. = ..()
.["categories"] = GLOB.achievement_categories
.["achievements"] = list()
.["highscore"] = list()
.["user_key"] = user.ckey

var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)
//This should be split into static data later
for(var/achievement_type in SSachievements.awards)
if(!SSachievements.awards[achievement_type].name) //No name? we a subtype.
var/datum/award/award = SSachievements.awards[achievement_type]
if(!award.name) //No name? we a subtype.
continue
if(isnull(data[achievement_type])) //We're still loading
continue
var/list/this = list(
"name" = SSachievements.awards[achievement_type].name,
"desc" = SSachievements.awards[achievement_type].desc,
"category" = SSachievements.awards[achievement_type].category,
"icon_class" = assets.icon_class_name(SSachievements.awards[achievement_type].icon),
var/list/award_data = list(
"name" = award.name,
"desc" = award.desc,
"category" = award.category,
"icon_class" = assets.icon_class_name(award.icon),
"value" = data[achievement_type],
"score" = ispath(achievement_type,/datum/award/score)
)
ret_data["achievements"] += list(this)

return ret_data
award_data += award.get_ui_data(user.ckey)
.["achievements"] += list(award_data)

/datum/achievement_data/ui_static_data(mob/user)
. = ..()
.["highscore"] = list()
for(var/score in SSachievements.scores)
var/datum/award/score/S = SSachievements.scores[score]
if(!S.name || !S.track_high_scores || !S.high_scores.len)
Expand Down
Loading

0 comments on commit 2aae3bb

Please sign in to comment.