Moves achievements from Hub to DB. (#47617)

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.
This commit is contained in:
AnturK
2019-11-26 08:35:28 +01:00
committed by oranges
parent 77c7c1ec55
commit a31c460dc0
16 changed files with 455 additions and 132 deletions

View File

@@ -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

View File

@@ -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 */;

View File

@@ -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 */;

View File

@@ -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

View File

@@ -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()
@@ -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
@@ -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)

View File

@@ -1,18 +1,16 @@
///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()
@@ -20,45 +18,51 @@
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)
///Gets the data for a specific achievement and caches it
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]
///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.
@@ -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)

View File

@@ -9,44 +9,50 @@
///What ID do we use on the hub?
var/hub_id
//Value returned on db connection failure, in case we want to differ 0 and nonexistent later on
var/default_value = FALSE
///This proc loads the achievement data from the hub.
/datum/award/proc/load(key)
return
if(!SSdbcore.Connect())
return default_value
if(!key || !hub_id || !name)
return default_value
var/raw_value = get_raw_value(key)
return parse_value(raw_value)
///This saves the changed data to the hub.
/datum/award/proc/save(key, value)
/datum/award/proc/get_changed_rows(key, value)
if(!hub_id || !key || !name)
return
return list("ckey" = "'[sanitizeSQL(key)]'","achievement_key" = "'[sanitizeSQL(hub_id)]'", "value" = "'[sanitizeSQL(value)]'")
///Get raw numerical achievement value from the database
/datum/award/proc/get_raw_value(key)
var/datum/DBQuery/Q = SSdbcore.NewQuery("SELECT value FROM [format_table_name("achievements")] WHERE ckey = '[sanitizeSQL(key)]' AND achievement_key = '[sanitizeSQL(hub_id)]'")
if(!Q.Execute(async = TRUE))
qdel(Q)
return 0
var/result = 0
if(Q.NextRow())
result = text2num(Q.item[1])
qdel(Q)
return result
//Should return sanitized value for achievement cache
/datum/award/proc/parse_value(raw_value)
return default_value
///Can be overriden for achievement specific events
/datum/award/proc/on_unlock(mob/user)
return
///Achievements are one-off awards for usually doing cool things.
/datum/award/achievement
desc = "Achievement for epic people"
///Can be overriden for achievement specific events
/datum/award/proc/on_unlock(mob/user)
return
/datum/award/achievement/save(key,value)
set waitfor = FALSE //Polling is latent so we don't wait for this proc
if(!SSachievements.hub_enabled)
return
if(!hub_id || !key)
return
if(value)
world.SetMedal(hub_id, key, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
else
world.ClearMedal(hub_id, key, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
/datum/award/achievement/load(key)
. = ..()
//Fallback
if(!SSachievements.hub_enabled)
return FALSE
if(!hub_id)
CRASH("Achievement without valid hub_id")
var/raw = world.GetMedal(hub_id, key, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
return isnull(raw) ? FALSE : raw
/datum/award/achievement/parse_value(raw_value)
return raw_value > 0
/datum/award/achievement/on_unlock(mob/user)
. = ..()
@@ -55,29 +61,28 @@
///Scores are for leaderboarded things, such as killcount of a specific boss
/datum/award/score
desc = "you did it sooo many times."
category = "Scores"
default_value = 0
/datum/award/score/save(key,value)
set waitfor = FALSE //Polling is latent so we don't wait for this proc
if(!SSachievements.hub_enabled)
return
if(!hub_id || !key)
return
var/track_high_scores = TRUE
var/list/high_scores = list()
var/list/R = list()
R[hub_id] = value
world.SetScores(key,R,CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
/datum/award/score/load(key)
set waitfor = FALSE //Polling is latent so we don't wait for this proc
/datum/award/score/New()
. = ..()
//Fallback
if(!SSachievements.hub_enabled)
return FALSE
if(!name) //Not a real achievement
return FALSE
if(!hub_id)
CRASH("Achievement without valid hub_id")
var/list/raw = world.GetScores(key, hub_id, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
return isnull(raw) ? 0 : raw[hub_id]
if(track_high_scores)
LoadHighScores()
/datum/award/score/proc/LoadHighScores()
var/datum/DBQuery/Q = SSdbcore.NewQuery("SELECT ckey,value FROM [format_table_name("achievements")] WHERE achievement_key = '[sanitizeSQL(hub_id)]' ORDER BY value DESC LIMIT 50")
if(!Q.Execute(async = TRUE))
qdel(Q)
return
else
while(Q.NextRow())
var/key = Q.item[1]
var/score = text2num(Q.item[2])
high_scores[key] = score
qdel(Q)
/datum/award/score/parse_value(raw_value)
return isnum(raw_value) ? raw_value : 0

View File

@@ -48,7 +48,7 @@ GLOBAL_LIST_INIT(tendrils, list())
last_tendril = FALSE
if(last_tendril && !(flags_1 & ADMIN_SPAWNED_1))
if(SSachievements.hub_enabled)
if(SSachievements.achievements_enabled)
for(var/mob/living/L in view(7,src))
if(L.stat || !L.client)
continue

View File

@@ -776,11 +776,11 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
if(!check_rights(R_DEBUG))
return
SSachievements.hub_enabled = !SSachievements.hub_enabled
SSachievements.achievements_enabled = !SSachievements.achievements_enabled
message_admins("<span class='adminnotice'>[key_name_admin(src)] [SSachievements.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.</span>")
message_admins("<span class='adminnotice'>[key_name_admin(src)] [SSachievements.achievements_enabled ? "disabled" : "enabled"] the medal hub lockout.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Medal Disable") // If...
log_admin("[key_name(src)] [SSachievements.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.")
log_admin("[key_name(src)] [SSachievements.achievements_enabled ? "disabled" : "enabled"] the medal hub lockout.")
/client/proc/view_runtimes()
set category = "Debug"

View File

@@ -478,8 +478,6 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
send2irc("Server", "[cheesy_message] (No admins online)")
player_details.achievements.save()
GLOB.ahelp_tickets.ClientLogout(src)
GLOB.directory -= ckey
GLOB.clients -= src
@@ -951,4 +949,4 @@ GLOBAL_LIST_EMPTY(external_rsc_urls)
/client/proc/give_award(achievement_type, mob/user)
return player_details.achievements.unlock(achievement_type, mob/user)
return player_details.achievements.unlock(achievement_type, user)

View File

@@ -138,7 +138,7 @@
ranged_cooldown = world.time + buffer_time
/mob/living/simple_animal/hostile/megafauna/proc/grant_achievement(medaltype, scoretype, crusher_kill, list/grant_achievement = list())
if(!achievement_type || (flags_1 & ADMIN_SPAWNED_1) || !SSachievements.hub_enabled) //Don't award medals if the medal type isn't set
if(!achievement_type || (flags_1 & ADMIN_SPAWNED_1) || !SSachievements.achievements_enabled) //Don't award medals if the medal type isn't set
return FALSE
if(!grant_achievement.len)
for(var/mob/living/L in view(7,src))

View File

@@ -21,7 +21,7 @@ export const classes = classNames => {
*/
export const normalizeChildren = children => {
if (Array.isArray(children)) {
return children.filter(value => value);
return children.flat().filter(value => value);
}
if (typeof children === 'object') {
return [children];

View File

@@ -1,6 +1,48 @@
import { Fragment } from 'inferno';
import { act } from '../byond';
import { Box, Tabs } from '../components';
import { Box, Tabs, Table, Icon } from '../components';
export const Achievement = props => {
const {
name,
desc,
icon_class,
value,
} = props;
return (
<tr key={name}>
<td style={{'padding': '6px'}}>
<Box className={icon_class} />
</td>
<td style={{'vertical-align': 'top'}}>
<h1>{name}</h1>
{desc}
<Box
color={value ? "good" : "bad"}
content={value ? "Unlocked" : "Locked"} />
</td>
</tr>);
};
export const Score = props => {
const {
name,
desc,
icon_class,
value,
} = props;
return (
<tr key={name}>
<td style={{'padding': '6px'}}>
<Box className={icon_class} />
</td>
<td style={{'vertical-align': 'top'}}>
<h1>{name}</h1>
{desc}
<Box
color={value > 0 ? "good" : "bad"}
content={value > 0 ? "Earned " + value + " times" : "Locked"} />
</td>
</tr>);
};
export const Achievements = props => {
const { state } = props;
@@ -15,23 +57,54 @@ export const Achievements = props => {
<Box as="Table">
{data.achievements
.filter(x => x.category === category)
.map(achievement => (
<tr key={achievement.name}>
<td style={{'padding': '6px'}}>
<Box className={achievement.icon_class} />
</td>
<td style={{'vertical-align': 'top'}}>
<h1>{achievement.name}</h1>
{achievement.desc}
<Box
color={achievement.achieved ? "good" : "bad"}
content={achievement.achieved ? "Unlocked" : "Locked"} />
</td>
</tr>
))}
.map(achievement => {
if (achievement.score) {
return (<Score
name={achievement.name}
desc={achievement.desc}
icon_class={achievement.icon_class}
value={achievement.value} />);
}
else {
return (<Achievement
name={achievement.name}
desc={achievement.desc}
icon_class={achievement.icon_class}
value={achievement.value} />);
}
})}
</Box>
</Tabs.Tab>
))}
<Tabs.Tab
label={"High Scores"}>
<Tabs vertical>
{data.highscore.map(highscore => {
return (
<Tabs.Tab key={highscore.name} label={highscore.name}>
<Table>
<Table.Row className="candystripe">
<Table.Cell color="label" textAlign="center">#</Table.Cell>
<Table.Cell color="label" textAlign="center">Key</Table.Cell>
<Table.Cell color="label" textAlign="center">Score</Table.Cell>
</Table.Row>
{ Object.keys(highscore.scores).map((key, index) => {
return (
<Table.Row className="candystripe" key={key} m={2}>
<Table.Cell color="label" textAlign="center">{index+1}</Table.Cell>
<Table.Cell color={key === data.user_ckey ? "green" : null} textAlign="center">
{(index === 0 && <Icon name="crown" color="gold" mr={2} />)}
{key}
{(index === 0 && <Icon name="crown" color="gold" ml={2} />)}
</Table.Cell>
<Table.Cell textAlign="center">{highscore.scores[key]}</Table.Cell>
</Table.Row>);
})}
</Table>
</Tabs.Tab>);
})}
</Tabs>
</Tabs.Tab>
</Tabs>
);
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,166 @@
//Misc Medal hub IDs
#define MEDAL_METEOR "Your Life Before Your Eyes"
#define MEDAL_PULSE "Jackpot"
#define MEDAL_TIMEWASTE "Overextended The Joke"
#define MEDAL_RODSUPLEX "Feat of Strength"
#define MEDAL_CLOWNCARKING "Round and Full"
#define MEDAL_THANKSALOT "The Best Driver"
#define MEDAL_HELBITALJANKEN "Hel-bent on Winning"
#define MEDAL_MATERIALCRAFT "Getting an Upgrade"
//Boss medals
// Medal hub IDs for boss medals (Pre-fixes)
#define BOSS_MEDAL_ANY "Boss Killer"
#define BOSS_MEDAL_MINER "Blood-drunk Miner Killer"
#define BOSS_MEDAL_BUBBLEGUM "Bubblegum Killer"
#define BOSS_MEDAL_COLOSSUS "Colossus Killer"
#define BOSS_MEDAL_DRAKE "Drake Killer"
#define BOSS_MEDAL_HIEROPHANT "Hierophant Killer"
#define BOSS_MEDAL_LEGION "Legion Killer"
#define BOSS_MEDAL_TENDRIL "Tendril Exterminator"
#define BOSS_MEDAL_SWARMERS "Swarmer Beacon Killer"
#define BOSS_MEDAL_MINER_CRUSHER "Blood-drunk Miner Crusher"
#define BOSS_MEDAL_BUBBLEGUM_CRUSHER "Bubblegum Crusher"
#define BOSS_MEDAL_COLOSSUS_CRUSHER "Colossus Crusher"
#define BOSS_MEDAL_DRAKE_CRUSHER "Drake Crusher"
#define BOSS_MEDAL_HIEROPHANT_CRUSHER "Hierophant Crusher"
#define BOSS_MEDAL_LEGION_CRUSHER "Legion Crusher"
#define BOSS_MEDAL_SWARMERS_CRUSHER "Swarmer Beacon Crusher"
// Medal hub IDs for boss-kill scores
#define BOSS_SCORE "Bosses Killed"
#define MINER_SCORE "BDMs Killed"
#define BUBBLEGUM_SCORE "Bubblegum Killed"
#define COLOSSUS_SCORE "Colossus Killed"
#define DRAKE_SCORE "Drakes Killed"
#define HIEROPHANT_SCORE "Hierophants Killed"
#define LEGION_SCORE "Legion Killed"
#define SWARMER_BEACON_SCORE "Swarmer Beacs Killed"
#define TENDRIL_CLEAR_SCORE "Tendrils Killed"
//Migration script generation
//Replace hub information and fire to generate hub_migration.sql script to use.
/mob/verb/generate_migration_script()
set name = "Generate Hub Migration Script"
var/hub_address = "REPLACEME"
var/hub_password = "REPLACEME"
var/list/valid_medals = list(
MEDAL_METEOR,
MEDAL_PULSE,
MEDAL_TIMEWASTE,
MEDAL_RODSUPLEX,
MEDAL_CLOWNCARKING,
MEDAL_THANKSALOT,
MEDAL_HELBITALJANKEN,
MEDAL_MATERIALCRAFT,
BOSS_MEDAL_ANY,
BOSS_MEDAL_MINER,
BOSS_MEDAL_BUBBLEGUM,
BOSS_MEDAL_COLOSSUS,
BOSS_MEDAL_DRAKE,
BOSS_MEDAL_HIEROPHANT,
BOSS_MEDAL_LEGION,
BOSS_MEDAL_TENDRIL,
BOSS_MEDAL_SWARMERS,
BOSS_MEDAL_MINER_CRUSHER,
BOSS_MEDAL_BUBBLEGUM_CRUSHER,
BOSS_MEDAL_COLOSSUS_CRUSHER,
BOSS_MEDAL_DRAKE_CRUSHER,
BOSS_MEDAL_HIEROPHANT_CRUSHER,
BOSS_MEDAL_LEGION_CRUSHER,
BOSS_MEDAL_SWARMERS_CRUSHER)
var/list/valid_scores = list(
BOSS_SCORE,
MINER_SCORE,
BUBBLEGUM_SCORE,
COLOSSUS_SCORE,
DRAKE_SCORE,
HIEROPHANT_SCORE,
LEGION_SCORE,
SWARMER_BEACON_SCORE,
TENDRIL_CLEAR_SCORE)
var/ach = "achievements" //IMPORTANT : ADD PREFIX HERE IF YOU'RE USING PREFIXED SCHEMA
var/outfile = file("hub_migration.sql")
fdel(outfile)
outfile << "BEGIN;"
var/perpage = 100
var/requested_page = 1
var/hub_url = replacetext(hub_address,".","/")
var/list/medal_data = list()
var/regex/datepart_regex = regex(@"[/\s]")
while(1)
world << "Fetching page [requested_page]"
var/list/result = world.Export("http://www.byond.com/games/[hub_url]?format=text&command=view_medals&per_page=[perpage]&page=[requested_page]")
if(!result)
return
var/data = file2text(result["CONTENT"])
var/regex/page_info = regex(@"page = (\d*)")
page_info.Find(data)
var/recieved_page = text2num(page_info.group[1])
if(recieved_page != requested_page) //out of entries
break
else
requested_page++
var/regex/R = regex(@'medal/\d+[\s\n]*key = "(.*)"[\s\n]*name = "(.*)"[\s\n]*desc = ".*"[\s\n]*icon = ".*"[\s\n]*earned = "(.*)"',"gm")
while(R.Find(data))
var/key = ckey(R.group[1])
var/medal = R.group[2]
var/list/dateparts = splittext(R.group[3],datepart_regex)
var/list/out_date = list(dateparts[3],dateparts[1],dateparts[2]) // YYYY/MM/DD
if(!valid_medals.Find(medal))
continue
if(!medal_data[key])
medal_data[key] = list()
medal_data[key][medal] = out_date.Join("/")
var/list/giant_list_of_ckeys = params2list(world.GetScores(null,null,hub_address,hub_password))
world << "Found [giant_list_of_ckeys.len] as upper scores count."
var/list/scores_data = list()
for(var/score in valid_scores)
var/recieved_count = 0
while(1)
world << "Fetching [score] scores, offset :[recieved_count] of [score]"
var/list/batch = params2list(world.GetScores(giant_list_of_ckeys.len,recieved_count,score,hub_address,hub_password))
world << "Fetched [batch.len] scores for [score]."
recieved_count += batch.len
if(!batch.len)
break
for(var/value in batch)
var/key = ckey(value)
if(!scores_data[key])
scores_data[key] = list()
if(isnum(batch[value]))
world << "NUMBER"
return
scores_data[key][score] = batch[value]
if(batch.len < 1000) //Out of scores anyway
break
var/i = 1
for(var/key in giant_list_of_ckeys)
world << "Generating entries for [key] [i]/[giant_list_of_ckeys.len]"
var/keyv = ckey(key) //Checkinf if you don't have any manually entered drop tables; juniors on your hub is good idea.
var/list/values = list()
for(var/cheevo in medal_data[keyv])
values += "('[keyv]','[cheevo]',1, '[medal_data[keyv][cheevo]]')"
for(var/score in scores_data[keyv])
values += "('[keyv]','[score]',[scores_data[keyv][score]],now())"
if(values.len)
var/list/keyline = list("INSERT INTO [ach](ckey,achievement_key,value,last_updated) VALUES")
keyline += values.Join(",")
keyline += ";"
outfile << keyline.Join()
i++
outfile << "END"

View File

@@ -0,0 +1,19 @@
// DM Environment file for HubMigrator.dme.
// All manual changes should be made outside the BEGIN_ and END_ blocks.
// New source code should be placed in .dm files: choose File/New --> Code File.
// BEGIN_INTERNALS
// END_INTERNALS
// BEGIN_FILE_DIR
#define FILE_DIR .
// END_FILE_DIR
// BEGIN_PREFERENCES
#define DEBUG
// END_PREFERENCES
// BEGIN_INCLUDE
#include "HubMigrator.dm"
// END_INCLUDE