Skip to content

Commit

Permalink
Moves achievements from Hub to DB. (tgstation#47617)
Browse files Browse the repository at this point in the history
Drops byond hub support and adds score and top 50 browsers.

Requires DB changes and manual creation of migration script if we want to keep old achievements so no random merges please.
  • Loading branch information
AnturK authored and optimumtact committed Nov 26, 2019
1 parent 77c7c1e commit a31c460
Show file tree
Hide file tree
Showing 16 changed files with 454 additions and 131 deletions.
21 changes: 18 additions & 3 deletions SQL/database_changelog.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255.

The latest database version is 5.3; The query to update the schema revision table is:
The latest database version is 5.4; The query to update the schema revision table is:

INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 3);
INSERT INTO `schema_revision` (`major`, `minor`) VALUES (5, 4);
or
INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 3);
INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 4);

In any query remember to add a prefix to the table names if you use one.


-----------------------------------------------------

Version 5.4, 5 October 2019 by Anturke
Added achievements table.
See hub migration verb in _achievement_data.dm for details on migrating.

CREATE TABLE `achievements` (
`ckey` VARCHAR(32) NOT NULL,
`achievement_key` VARCHAR(32) NOT NULL,
`value` INT NULL,
`last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`ckey`,`achievement_key`)
) ENGINE=InnoDB;

----------------------------------------------------

Version 5.3, 6 July 2019, by Atlanta-Ned
Expand Down
12 changes: 12 additions & 0 deletions SQL/tgstation_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,18 @@ CREATE TABLE `stickyban_matched_cid` (
PRIMARY KEY (`stickyban`, `matched_cid`)
) ENGINE=InnoDB;

--
-- Table structure for table `achievements`
--
DROP TABLE IF EXISTS `achievements`;
CREATE TABLE `achievements` (
`ckey` VARCHAR(32) NOT NULL,
`achievement_key` VARCHAR(32) NOT NULL,
`value` INT NULL,
`last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`ckey`,`achievement_key`)
) ENGINE=InnoDB;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
Expand Down
12 changes: 12 additions & 0 deletions SQL/tgstation_schema_prefixed.sql
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,18 @@ CREATE TABLE `SS13_stickyban_matched_cid` (
PRIMARY KEY (`stickyban`, `matched_cid`)
) ENGINE=InnoDB;

--
-- Table structure for table `SS13_achievements`
--
DROP TABLE IF EXISTS `SS13_achievements`;
CREATE TABLE `SS13_achievements` (
`ckey` VARCHAR(32) NOT NULL,
`achievement_key` VARCHAR(32) NOT NULL,
`value` INT NULL,
`last_updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`ckey`,`achievement_key`)
) ENGINE=InnoDB;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
Expand Down
2 changes: 1 addition & 1 deletion code/__DEFINES/subsystems.dm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* make sure you add an update to the schema_version stable in the db changelog
*/
#define DB_MINOR_VERSION 3
#define DB_MINOR_VERSION 4


//! ## Timing subsystem
Expand Down
24 changes: 15 additions & 9 deletions code/controllers/subsystem/achievements.dm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ SUBSYSTEM_DEF(achievements)
name = "Achievements"
flags = SS_NO_FIRE
init_order = INIT_ORDER_ACHIEVEMENTS
var/hub_enabled = FALSE
var/achievements_enabled = FALSE

///List of achievements
var/list/datum/award/achievement/achievements = list()
Expand All @@ -12,9 +12,10 @@ SUBSYSTEM_DEF(achievements)
var/list/datum/award/awards = list()

/datum/controller/subsystem/achievements/Initialize(timeofday)
if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password))
hub_enabled = TRUE

if(!SSdbcore.Connect())
return
achievements_enabled = TRUE

for(var/T in subtypesof(/datum/award/achievement))
var/instance = new T
achievements[T] = instance
Expand All @@ -33,9 +34,14 @@ SUBSYSTEM_DEF(achievements)
return ..()

/datum/controller/subsystem/achievements/Shutdown()
save_achievements_to_hub()
save_achievements_to_db()

/datum/controller/subsystem/achievements/proc/save_achievements_to_hub()
for(var/i in GLOB.clients)
var/client/C = i
C.player_details.achievements.save()
/datum/controller/subsystem/achievements/proc/save_achievements_to_db()
var/list/cheevos_to_save = list()
for(var/ckey in GLOB.player_details)
var/datum/player_details/PD = GLOB.player_details[ckey]
if(!PD || !PD.achievements)
continue
cheevos_to_save += PD.achievements.get_changed_data()

SSdbcore.MassInsert(format_table_name("achievements"),cheevos_to_save,duplicate_key = TRUE)
97 changes: 57 additions & 40 deletions code/datums/achievements/_achievement_data.dm
Original file line number Diff line number Diff line change
@@ -1,64 +1,68 @@
///Datum that handles
/datum/achievement_data
///Ckey of this achievement data's owner
var/key
var/owner_ckey
///Up to date list of all achievements and their info.
var/data = list()
///Original status of achievement.
var/original_cached_data = list()
///All icons for the UI of achievements
var/list/AchievementIcons = null
///Have we done our set-up yet?
var/initialized = FALSE

/datum/achievement_data/New(key)
src.key = key
/datum/achievement_data/New(ckey)
owner_ckey = ckey
if(SSachievements.initialized && !initialized)
InitializeData()

/datum/achievement_data/proc/InitializeData()
initialized = TRUE
load_all_achievements() //So we know which achievements we have unlocked so far.

var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)
AchievementIcons = list()
for(var/achievement_type in SSachievements.achievements)
var/datum/award/achievement = SSachievements.achievements[achievement_type]
var/list/SL = list()
SL["htmltag"] = assets.icon_tag(achievement.icon)
AchievementIcons[achievement.name] += list(SL)

///Saves any out-of-date achievements to the hub.
/datum/achievement_data/proc/save()
///Gets list of changed rows in MassInsert format
/datum/achievement_data/proc/get_changed_data()
. = list()
for(var/T in data)
var/datum/award/A = SSachievements.awards[T]

if(data[T] != original_cached_data[T])//If our data from before is not the same as now, save it to the hub. This check prevents unnecesary polling.
A.save(key,data[T])

///Loads data for all achievements to the caches.
/datum/achievement_data/proc/load_all()
for(var/T in subtypesof(/datum/award))
get_data(T)
if(data[T] != original_cached_data[T])//If our data from before is not the same as now, save it to db.
var/deets = A.get_changed_rows(owner_ckey,data[T])
if(deets)
. += list(deets)

/datum/achievement_data/proc/load_all_achievements()
set waitfor = FALSE
for(var/T in subtypesof(/datum/award/achievement))
get_data(T)

var/list/kv = list()
var/datum/DBQuery/Query = SSdbcore.NewQuery("SELECT achievement_key,value FROM [format_table_name("achievements")] WHERE ckey = '[sanitizeSQL(owner_ckey)]'")
if(!Query.Execute())
qdel(Query)
return
while(Query.NextRow())
var/key = Query.item[1]
var/value = text2num(Query.item[2])
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
continue
if(!data[T])
data[T] = A.parse_value(kv[A.hub_id])
original_cached_data[T] = data[T]

///Gets the data for a specific achievement and caches it
///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)
var/datum/award/A = SSachievements.awards[achievement_type]
if(!A.name)
return FALSE
if(!data[achievement_type])
data[achievement_type] = A.load(key)
data[achievement_type] = A.load(owner_ckey)
original_cached_data[achievement_type] = data[achievement_type]

///Unlocks an achievement of a specific type.
/datum/achievement_data/proc/unlock(achievement_type, mob/user)
var/datum/award/A = SSachievements.awards[achievement_type]
get_data(achievement_type) //Get the current status first
get_data(achievement_type) //Get the current status first if necessary
if(istype(A, /datum/award/achievement))
data[achievement_type] = TRUE
A.on_unlock(user) //Only on default achievement, as scores keep going up.
Expand Down Expand Up @@ -92,32 +96,45 @@

/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", "Misc")
ret_data["categories"] = list("Bosses", "Misc" , "Scores")
ret_data["achievements"] = list()
ret_data["user_key"] = user.ckey

var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)

for(var/achievement_type in SSachievements.achievements)
if(!SSachievements.achievements[achievement_type].name) //No name? we a subtype.
//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.
continue
if(isnull(data[achievement_type])) //We're still loading
continue
var/list/this = list(
"name" = SSachievements.achievements[achievement_type].name,
"desc" = SSachievements.achievements[achievement_type].desc,
"category" = SSachievements.achievements[achievement_type].category,
"icon_class" = assets.icon_class_name(SSachievements.achievements[achievement_type].icon),
"achieved" = data[achievement_type]
)

"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),
"value" = data[achievement_type],
"score" = ispath(achievement_type,/datum/award/score)
)
ret_data["achievements"] += list(this)

return ret_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)
continue
.["highscore"] += list(list("name" = S.name,"scores" = S.high_scores))

/client/verb/checkachievements()
set category = "OOC"
set name = "Check achievements"
set desc = "See all of your achievements!"

player_details.achievements.ui_interact(usr)



/mob/verb/gimme_jackpot()
client.give_award(/datum/award/achievement/misc/time_waste,src)
Loading

0 comments on commit a31c460

Please sign in to comment.