From a31c460dc0efad9cfa9ee41477ec27d9d19b0985 Mon Sep 17 00:00:00 2001 From: AnturK Date: Tue, 26 Nov 2019 08:35:28 +0100 Subject: [PATCH] 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. --- SQL/database_changelog.txt | 21 ++- SQL/tgstation_schema.sql | 12 ++ SQL/tgstation_schema_prefixed.sql | 12 ++ code/__DEFINES/subsystems.dm | 2 +- code/controllers/subsystem/achievements.dm | 24 ++- code/datums/achievements/_achievement_data.dm | 97 +++++----- code/datums/achievements/_awards.dm | 105 +++++------ .../structures/lavaland/necropolis_tendril.dm | 2 +- code/modules/admin/verbs/debug.dm | 6 +- code/modules/client/client_procs.dm | 4 +- .../hostile/megafauna/megafauna.dm | 2 +- tgui-next/packages/common/react.js | 2 +- .../packages/tgui/interfaces/Achievements.js | 107 +++++++++-- tgui-next/packages/tgui/public/tgui.bundle.js | 4 +- tools/HubMigrator/HubMigrator.dm | 166 ++++++++++++++++++ tools/HubMigrator/HubMigrator.dme | 19 ++ 16 files changed, 454 insertions(+), 131 deletions(-) create mode 100644 tools/HubMigrator/HubMigrator.dm create mode 100644 tools/HubMigrator/HubMigrator.dme diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt index 770d066455da9..8ac8a1bb0e09a 100644 --- a/SQL/database_changelog.txt +++ b/SQL/database_changelog.txt @@ -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 diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql index b93c14bec8e24..1738f40151f7b 100644 --- a/SQL/tgstation_schema.sql +++ b/SQL/tgstation_schema.sql @@ -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 */; diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index 5c2dc0105554b..37d268d4c96fd 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -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 */; diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index b304b2bd8ef6c..22ddbc6619375 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -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 diff --git a/code/controllers/subsystem/achievements.dm b/code/controllers/subsystem/achievements.dm index f73a0065b557d..369ffa6f6724a 100644 --- a/code/controllers/subsystem/achievements.dm +++ b/code/controllers/subsystem/achievements.dm @@ -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) diff --git a/code/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm index 7fc0578006758..fd19f8f0b10b5 100644 --- a/code/datums/achievements/_achievement_data.dm +++ b/code/datums/achievements/_achievement_data.dm @@ -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) + + 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. @@ -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) diff --git a/code/datums/achievements/_awards.dm b/code/datums/achievements/_awards.dm index d4c2d1387c23b..6c944118e96e2 100644 --- a/code/datums/achievements/_awards.dm +++ b/code/datums/achievements/_awards.dm @@ -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) - return +/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)]'") -///Achievements are one-off awards for usually doing cool things. -/datum/award/achievement - desc = "Achievement for epic people" +///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 -/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)) +///Achievements are one-off awards for usually doing cool things. +/datum/award/achievement + desc = "Achievement for epic people" -/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 diff --git a/code/game/objects/structures/lavaland/necropolis_tendril.dm b/code/game/objects/structures/lavaland/necropolis_tendril.dm index fef7270cef6fa..ae5b8b4776d8f 100644 --- a/code/game/objects/structures/lavaland/necropolis_tendril.dm +++ b/code/game/objects/structures/lavaland/necropolis_tendril.dm @@ -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 diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 8cd8165f5addb..0f1a1e9eb8239 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -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("[key_name_admin(src)] [SSachievements.hub_enabled ? "disabled" : "enabled"] the medal hub lockout.") + message_admins("[key_name_admin(src)] [SSachievements.achievements_enabled ? "disabled" : "enabled"] the medal hub lockout.") 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" diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index ee981da227b7d..0c603c07f4695 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -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) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index e7e79f5c5a228..91dbe4e6304c5 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -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)) diff --git a/tgui-next/packages/common/react.js b/tgui-next/packages/common/react.js index e2731aebbe574..0828bb8864d86 100644 --- a/tgui-next/packages/common/react.js +++ b/tgui-next/packages/common/react.js @@ -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]; diff --git a/tgui-next/packages/tgui/interfaces/Achievements.js b/tgui-next/packages/tgui/interfaces/Achievements.js index 1ed2a1ce77683..e1ced112867bc 100644 --- a/tgui-next/packages/tgui/interfaces/Achievements.js +++ b/tgui-next/packages/tgui/interfaces/Achievements.js @@ -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 ( + + + + + +

{name}

+ {desc} + + + ); +}; + +export const Score = props => { + const { + name, + desc, + icon_class, + value, + } = props; + return ( + + + + + +

{name}

+ {desc} + 0 ? "good" : "bad"} + content={value > 0 ? "Earned " + value + " times" : "Locked"} /> + + ); +}; export const Achievements = props => { const { state } = props; @@ -15,23 +57,54 @@ export const Achievements = props => { {data.achievements .filter(x => x.category === category) - .map(achievement => ( - - - - - -

{achievement.name}

- {achievement.desc} - - - - ))} + .map(achievement => { + if (achievement.score) { + return (); + } + else { + return (); + } + })} ))} + + + {data.highscore.map(highscore => { + return ( + + + + # + Key + Score + + { Object.keys(highscore.scores).map((key, index) => { + return ( + + {index+1} + + {(index === 0 && )} + {key} + {(index === 0 && )} + + {highscore.scores[key]} + ); + })} +
+
); + })} +
+
); }; diff --git a/tgui-next/packages/tgui/public/tgui.bundle.js b/tgui-next/packages/tgui/public/tgui.bundle.js index feea7d5b4dd5e..2048886c7f3cf 100644 --- a/tgui-next/packages/tgui/public/tgui.bundle.js +++ b/tgui-next/packages/tgui/public/tgui.bundle.js @@ -1,3 +1,3 @@ -!function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=159)}([function(e,t,n){"use strict";var o=n(5),r=n(19).f,a=n(23),i=n(21),c=n(89),l=n(119),u=n(61);e.exports=function(e,t){var n,s,d,f,p,m=e.target,h=e.global,g=e.stat;if(n=h?o:g?o[m]||c(m,{}):(o[m]||{}).prototype)for(s in t){if(f=t[s],d=e.noTargetGet?(p=r(n,s))&&p.value:n[s],!u(h?s:m+(g?".":"#")+s,e.forced)&&d!==undefined){if(typeof f==typeof d)continue;l(f,d)}(e.sham||d&&d.sham)&&a(f,"sham",!0),i(n,s,f,e)}}},function(e,t,n){"use strict";t.__esModule=!0;var o=n(379);Object.keys(o).forEach((function(e){"default"!==e&&"__esModule"!==e&&(t[e]=o[e])}))},function(e,t,n){"use strict";t.__esModule=!0,t.winset=t.winget=t.act=t.runCommand=t.callByondAsync=t.callByond=t.tridentVersion=void 0;var o,r=n(39),a=(o=navigator.userAgent.match(/Trident\/(\d+).+?;/i)[1])?parseInt(o,10):null;t.tridentVersion=a;var i=function(e,t){return void 0===t&&(t={}),"byond://"+e+"?"+(0,r.buildQueryString)(t)},c=function(e,t){void 0===t&&(t={}),window.location.href=i(e,t)};t.callByond=c;var l=function(e,t){void 0===t&&(t={}),window.__callbacks__=window.__callbacks__||[];var n=window.__callbacks__.length,o=new Promise((function(e){window.__callbacks__.push(e)}));return window.location.href=i(e,Object.assign({},t,{callback:"__callbacks__["+n+"]"})),o};t.callByondAsync=l;t.runCommand=function(e){return c("winset",{command:e})};t.act=function(e,t,n){return void 0===n&&(n={}),c("",Object.assign({src:e,action:t},n))};var u=function(e,t){var n;return regeneratorRuntime.async((function(o){for(;;)switch(o.prev=o.next){case 0:return o.next=2,regeneratorRuntime.awrap(l("winget",{id:e,property:t}));case 2:return n=o.sent,o.abrupt("return",n[t]);case 4:case"end":return o.stop()}}))};t.winget=u;t.winset=function(e,t,n){var o;return c("winset",((o={})[e+"."+t]=n,o))}},function(e,t,n){"use strict";t.__esModule=!0,t.Chart=t.Tooltip=t.Toast=t.TitleBar=t.Tabs=t.Table=t.Section=t.ProgressBar=t.NumberInput=t.NoticeBox=t.LabeledList=t.Input=t.Icon=t.Grid=t.Flex=t.Dimmer=t.ColorBox=t.Button=t.Box=t.BlockQuote=t.AnimatedNumber=void 0;var o=n(154);t.AnimatedNumber=o.AnimatedNumber;var r=n(384);t.BlockQuote=r.BlockQuote;var a=n(18);t.Box=a.Box;var i=n(155);t.Button=i.Button;var c=n(386);t.ColorBox=c.ColorBox;var l=n(387);t.Dimmer=l.Dimmer;var u=n(388);t.Flex=u.Flex;var s=n(389);t.Grid=s.Grid;var d=n(112);t.Icon=d.Icon;var f=n(390);t.Input=f.Input;var p=n(87);t.LabeledList=p.LabeledList;var m=n(391);t.NoticeBox=m.NoticeBox;var h=n(70);t.NumberInput=h.NumberInput;var g=n(392);t.ProgressBar=g.ProgressBar;var C=n(393);t.Section=C.Section;var b=n(157);t.Table=b.Table;var v=n(394);t.Tabs=v.Tabs;var N=n(395);t.TitleBar=N.TitleBar;var y=n(113);t.Toast=y.Toast;var V=n(156);t.Tooltip=V.Tooltip;var _=n(396);t.Chart=_.Chart},function(e,t,n){"use strict";e.exports=function(e){try{return!!e()}catch(t){return!0}}},function(e,t,n){"use strict";(function(t){var n=function(e){return e&&e.Math==Math&&e};e.exports=n("object"==typeof globalThis&&globalThis)||n("object"==typeof window&&window)||n("object"==typeof self&&self)||n("object"==typeof t&&t)||Function("return this")()}).call(this,n(115))},function(e,t,n){"use strict";e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){"use strict";var o,r=n(9),a=n(5),i=n(6),c=n(15),l=n(74),u=n(23),s=n(21),d=n(12).f,f=n(34),p=n(52),m=n(11),h=n(58),g=a.DataView,C=g&&g.prototype,b=a.Int8Array,v=b&&b.prototype,N=a.Uint8ClampedArray,y=N&&N.prototype,V=b&&f(b),_=v&&f(v),x=Object.prototype,k=x.isPrototypeOf,w=m("toStringTag"),L=h("TYPED_ARRAY_TAG"),B=!(!a.ArrayBuffer||!g),S=B&&!!p&&"Opera"!==l(a.opera),I=!1,T={Int8Array:1,Uint8Array:1,Uint8ClampedArray:1,Int16Array:2,Uint16Array:2,Int32Array:4,Uint32Array:4,Float32Array:4,Float64Array:8},A=function(e){var t=l(e);return"DataView"===t||c(T,t)},E=function(e){return i(e)&&c(T,l(e))};for(o in T)a[o]||(S=!1);if((!S||"function"!=typeof V||V===Function.prototype)&&(V=function(){throw TypeError("Incorrect invocation")},S))for(o in T)a[o]&&p(a[o],V);if((!S||!_||_===x)&&(_=V.prototype,S))for(o in T)a[o]&&p(a[o].prototype,_);if(S&&f(y)!==_&&p(y,_),r&&!c(_,w))for(o in I=!0,d(_,w,{get:function(){return i(this)?this[L]:undefined}}),T)a[o]&&u(a[o],L,o);B&&p&&f(C)!==x&&p(C,x),e.exports={NATIVE_ARRAY_BUFFER:B,NATIVE_ARRAY_BUFFER_VIEWS:S,TYPED_ARRAY_TAG:I&&L,aTypedArray:function(e){if(E(e))return e;throw TypeError("Target is not a typed array")},aTypedArrayConstructor:function(e){if(p){if(k.call(V,e))return e}else for(var t in T)if(c(T,o)){var n=a[t];if(n&&(e===n||k.call(n,e)))return e}throw TypeError("Target is not a typed array constructor")},exportProto:function(e,t,n){if(r){if(n)for(var o in T){var i=a[o];i&&c(i.prototype,e)&&delete i.prototype[e]}_[e]&&!n||s(_,e,n?t:S&&v[e]||t)}},exportStatic:function(e,t,n){var o,i;if(r){if(p){if(n)for(o in T)(i=a[o])&&c(i,e)&&delete i[e];if(V[e]&&!n)return;try{return s(V,e,n?t:S&&b[e]||t)}catch(l){}}for(o in T)!(i=a[o])||i[e]&&!n||s(i,e,t)}},isView:A,isTypedArray:E,TypedArray:V,TypedArrayPrototype:_}},function(e,t,n){"use strict";var o=n(6);e.exports=function(e){if(!o(e))throw TypeError(String(e)+" is not an object");return e}},function(e,t,n){"use strict";var o=n(4);e.exports=!o((function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}))},function(e,t,n){"use strict";var o=n(27),r=Math.min;e.exports=function(e){return e>0?r(o(e),9007199254740991):0}},function(e,t,n){"use strict";var o=n(5),r=n(57),a=n(58),i=n(122),c=o.Symbol,l=r("wks");e.exports=function(e){return l[e]||(l[e]=i&&c[e]||(i?c:a)("Symbol."+e))}},function(e,t,n){"use strict";var o=n(9),r=n(116),a=n(8),i=n(30),c=Object.defineProperty;t.f=o?c:function(e,t,n){if(a(e),t=i(t,!0),a(n),r)try{return c(e,t,n)}catch(o){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},function(e,t,n){"use strict";var o=n(20);e.exports=function(e){return Object(o(e))}},function(e,t,n){"use strict";t.__esModule=!0,t.isFalsy=t.pureComponentHooks=t.shallowDiffers=t.normalizeChildren=t.classes=void 0;t.classes=function(e){for(var t="",n=0;n_;_++)if((f||_ in N)&&(b=y(C=N[_],_,v),e))if(t)k[_]=b;else if(b)switch(e){case 3:return!0;case 5:return C;case 6:return _;case 2:l.call(k,C)}else if(s)return!1;return d?-1:u||s?s:k}};e.exports={forEach:u(0),map:u(1),filter:u(2),some:u(3),every:u(4),find:u(5),findIndex:u(6)}},function(e,t,n){"use strict";t.__esModule=!0,t.toFixed=t.round=t.clamp=void 0;t.clamp=function(e,t,n){return void 0===t&&(t=0),void 0===n&&(n=1),Math.max(t,Math.min(e,n))};t.round=function(e){return Math.round(e)};t.toFixed=function(e,t){return void 0===t&&(t=0),Number(e).toFixed(t)}},function(e,t,n){"use strict";t.__esModule=!0,t.Box=t.computeBoxProps=t.unit=void 0;var o=n(1),r=n(14),a=n(385),i=n(38);function c(e,t){if(null==e)return{};var n,o,r={},a=Object.keys(e);for(o=0;o=0||(r[n]=e[n]);return r}var l=function(e){return"string"==typeof e?e:"number"==typeof e?6*e+"px":void 0};t.unit=l;var u=function(e){return"string"==typeof e&&i.CSS_COLORS.includes(e)},s=function(e){return function(t,n){(0,r.isFalsy)(n)||(t[e]=n)}},d=function(e){return function(t,n){(0,r.isFalsy)(n)||(t[e]=l(n))}},f=function(e,t){return function(n,o){(0,r.isFalsy)(o)||(n[e]=t)}},p=function(e,t){return function(n,o){if(!(0,r.isFalsy)(o))for(var a=0;a0&&(t.style=l),t};t.computeBoxProps=g;var C=function(e){var t=e.as,n=void 0===t?"div":t,i=e.className,l=e.content,s=e.children,d=c(e,["as","className","content","children"]),f=e.textColor||e.color,p=e.backgroundColor;if("function"==typeof s)return s(g(e));var m=g(d);return(0,o.createVNode)(a.VNodeFlags.HtmlElement,n,(0,r.classes)([i,u(f)&&"color-"+f,u(p)&&"color-bg-"+p]),l||s,a.ChildFlags.UnknownChildren,m)};t.Box=C,C.defaultHooks=r.pureComponentHooks;var b=function(e){var t=e.children,n=c(e,["children"]);return(0,o.normalizeProps)((0,o.createComponentVNode)(2,C,Object.assign({position:"relative"},n,{children:(0,o.createComponentVNode)(2,C,{fillPositionedParent:!0,children:t})})))};b.defaultHooks=r.pureComponentHooks,C.Forced=b},function(e,t,n){"use strict";var o=n(9),r=n(72),a=n(45),i=n(22),c=n(30),l=n(15),u=n(116),s=Object.getOwnPropertyDescriptor;t.f=o?s:function(e,t){if(e=i(e),t=c(t,!0),u)try{return s(e,t)}catch(n){}if(l(e,t))return a(!r.f.call(e,t),e[t])}},function(e,t,n){"use strict";e.exports=function(e){if(e==undefined)throw TypeError("Can't call method on "+e);return e}},function(e,t,n){"use strict";var o=n(5),r=n(57),a=n(23),i=n(15),c=n(89),l=n(117),u=n(32),s=u.get,d=u.enforce,f=String(l).split("toString");r("inspectSource",(function(e){return l.call(e)})),(e.exports=function(e,t,n,r){var l=!!r&&!!r.unsafe,u=!!r&&!!r.enumerable,s=!!r&&!!r.noTargetGet;"function"==typeof n&&("string"!=typeof t||i(n,"name")||a(n,"name",t),d(n).source=f.join("string"==typeof t?t:"")),e!==o?(l?!s&&e[t]&&(u=!0):delete e[t],u?e[t]=n:a(e,t,n)):u?e[t]=n:c(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&s(this).source||l.call(this)}))},function(e,t,n){"use strict";var o=n(56),r=n(20);e.exports=function(e){return o(r(e))}},function(e,t,n){"use strict";var o=n(9),r=n(12),a=n(45);e.exports=o?function(e,t,n){return r.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t,n){"use strict";var o=n(120),r=n(15),a=n(126),i=n(12).f;e.exports=function(e){var t=o.Symbol||(o.Symbol={});r(t,e)||i(t,e,{value:a.f(e)})}},function(e,t,n){"use strict";var o=n(20),r=/"/g;e.exports=function(e,t,n,a){var i=String(o(e)),c="<"+t;return""!==n&&(c+=" "+n+'="'+String(a).replace(r,""")+'"'),c+">"+i+""}},function(e,t,n){"use strict";var o=n(4);e.exports=function(e){return o((function(){var t=""[e]('"');return t!==t.toLowerCase()||t.split('"').length>3}))}},function(e,t,n){"use strict";var o=Math.ceil,r=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?r:o)(e)}},function(e,t,n){"use strict";e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t,n){"use strict";var o={}.toString;e.exports=function(e){return o.call(e).slice(8,-1)}},function(e,t,n){"use strict";var o=n(6);e.exports=function(e,t){if(!o(e))return e;var n,r;if(t&&"function"==typeof(n=e.toString)&&!o(r=n.call(e)))return r;if("function"==typeof(n=e.valueOf)&&!o(r=n.call(e)))return r;if(!t&&"function"==typeof(n=e.toString)&&!o(r=n.call(e)))return r;throw TypeError("Can't convert object to primitive value")}},function(e,t,n){"use strict";t.__esModule=!0,t.zipWith=t.zip=t.reduce=t.product=t.sortBy=t.map=t.toArray=void 0;t.toArray=function(e){if(Array.isArray(e))return e;if("object"==typeof e){var t=Object.prototype.hasOwnProperty,n=[];for(var o in e)t.call(e,o)&&n.push(e[o]);return n}return[]};var o=function(e){return function(t){if(null===t&&t===undefined)return t;if(Array.isArray(t)){for(var n=[],o=0;oc)return 1}return 0};t.sortBy=function(){for(var e=arguments.length,t=new Array(e),n=0;n2?n-2:0),a=2;a=i){var c=[t].concat(r).map((function(e){return"string"==typeof e?e:e instanceof Error?e.stack||String(e):JSON.stringify(e)})).filter((function(e){return e})).join(" ")+"\nUser Agent: "+navigator.userAgent;(0,o.act)(window.__ref__,"tgui:log",{log:c})}};t.createLogger=function(e){return{debug:function(){for(var t=arguments.length,n=new Array(t),o=0;on;)r[n]=t[n++];return r},K=function(e,t){T(e,t,{get:function(){return S(this)[t]}})},Y=function(e){var t;return e instanceof O||"ArrayBuffer"==(t=C(e))||"SharedArrayBuffer"==t},q=function(e,t){return H(e)&&"symbol"!=typeof t&&t in e&&String(+t)==String(t)},W=function(e,t){return q(e,t=h(t,!0))?s(2,e[t]):A(e,t)},G=function(e,t,n){return!(q(e,t=h(t,!0))&&b(n)&&g(n,"value"))||g(n,"get")||g(n,"set")||n.configurable||g(n,"writable")&&!n.writable||g(n,"enumerable")&&!n.enumerable?T(e,t,n):(e[t]=n.value,e)};a?(F||(w.f=W,k.f=G,K(D,"buffer"),K(D,"byteOffset"),K(D,"byteLength"),K(D,"length")),o({target:"Object",stat:!0,forced:!F},{getOwnPropertyDescriptor:W,defineProperty:G}),e.exports=function(e,t,n,a){var c=e+(a?"Clamped":"")+"Array",l="get"+e,s="set"+e,h=r[c],g=h,C=g&&g.prototype,k={},w=function(e,n){var o=S(e);return o.view[l](n*t+o.byteOffset,!0)},L=function(e,n,o){var r=S(e);a&&(o=(o=E(o))<0?0:o>255?255:255&o),r.view[s](n*t+r.byteOffset,o,!0)},A=function(e,t){T(e,t,{get:function(){return w(this,t)},set:function(e){return L(this,t,e)},enumerable:!0})};F?i&&(g=n((function(e,n,o,r){return u(e,g,c),B(b(n)?Y(n)?r!==undefined?new h(n,m(o,t),r):o!==undefined?new h(n,m(o,t)):new h(n):H(n)?U(g,n):V.call(g,n):new h(p(n)),e,g)})),N&&N(g,j),_(y(h),(function(e){e in g||d(g,e,h[e])})),g.prototype=C):(g=n((function(e,n,o,r){u(e,g,c);var a,i,l,s=0,d=0;if(b(n)){if(!Y(n))return H(n)?U(g,n):V.call(g,n);a=n,d=m(o,t);var h=n.byteLength;if(r===undefined){if(h%t)throw P("Wrong length");if((i=h-d)<0)throw P("Wrong length")}else if((i=f(r)*t)+d>h)throw P("Wrong length");l=i/t}else l=p(n),a=new O(i=l*t);for(I(e,{buffer:a,byteOffset:d,byteLength:i,length:l,view:new M(a)});s=r.length)break;c=r[i++]}else{if((i=r.next()).done)break;c=i.value}for(var l=c,u=0;u",apos:"'"};return e.replace(/
/gi,"\n").replace(/<\/?[a-z0-9-_]+[^>]*>/gi,"").replace(/&(nbsp|amp|quot|lt|gt|apos);/g,(function(e,n){return t[n]})).replace(/&#?([0-9]+);/gi,(function(e,t){var n=parseInt(t,10);return String.fromCharCode(n)})).replace(/&#x?([0-9a-f]+);/gi,(function(e,t){var n=parseInt(t,16);return String.fromCharCode(n)}))};t.buildQueryString=function(e){return Object.keys(e).map((function(t){return encodeURIComponent(t)+"="+encodeURIComponent(e[t])})).join("&")}},function(e,t,n){"use strict";var o=n(27),r=Math.max,a=Math.min;e.exports=function(e,t){var n=o(e);return n<0?r(n+t,0):a(n,t)}},function(e,t,n){"use strict";var o=n(8),r=n(123),a=n(91),i=n(59),c=n(124),l=n(88),u=n(73)("IE_PROTO"),s="prototype",d=function(){},f=function(){var e,t=l("iframe"),n=a.length;for(t.style.display="none",c.appendChild(t),t.src=String("javascript:"),(e=t.contentWindow.document).open(),e.write("