mirror of
https://github.com/fulpstation/fulpstation.git
synced 2025-12-09 16:09:15 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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 */;
|
||||
|
||||
@@ -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 */;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
2
tgui-next/packages/common/react.js
vendored
2
tgui-next/packages/common/react.js
vendored
@@ -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];
|
||||
|
||||
@@ -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
166
tools/HubMigrator/HubMigrator.dm
Normal file
166
tools/HubMigrator/HubMigrator.dm
Normal 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"
|
||||
19
tools/HubMigrator/HubMigrator.dme
Normal file
19
tools/HubMigrator/HubMigrator.dme
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user