diff --git a/code/__DEFINES/context_flags.dm b/code/__DEFINES/context_flags.dm new file mode 100644 index 00000000000..94b43de769e --- /dev/null +++ b/code/__DEFINES/context_flags.dm @@ -0,0 +1,7 @@ +#define FLAG_PM "Private Message" +#define FLAG_SM "Subtle Message" +#define FLAG_GIB "Gib" +#define FLAG_JUMP "Jump To" +#define FLAG_JUMP_GHOST "Jump To As Ghost" +#define FLAG_PP "Player Panel" +#define FLAG_VV "View Variables" diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm index 44349ca7318..3b0f75fdf58 100644 --- a/code/__HELPERS/files.dm +++ b/code/__HELPERS/files.dm @@ -1,8 +1,3 @@ -//Sends resource files to client cache -/client/proc/getFiles(...) - for(var/file in args) - src << browse_rsc(file) - /client/proc/browse_files(root="data/logs/", max_iterations=10, list/valid_extensions=list("txt","log","htm", "html")) var/path = root diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 5b1d03e2793..85c66defafa 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -56,11 +56,10 @@ if(B.allow_random) GLOB.bark_random_list[B.id] = path - // Backgrounds - for (var/path in subtypesof(/datum/background)) - var/datum/background/background = new path() - GLOB.backgrounds[path] = background - sortList(GLOB.backgrounds, GLOBAL_PROC_REF(cmp_text_dsc)) + // Loadout items + for (var/path in subtypesof(/datum/loadout_item)) + var/datum/loadout_item/loadout_item = new path() + GLOB.loadout_items[path] = loadout_item //creates every subtype of prototype (excluding prototype) and adds it to list L. //if no list/L is provided, one is created. diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index 08d20391770..01f30f772b7 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1112,6 +1112,11 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 alpha += 25 obj_flags &= ~FROZEN +/// Generate a filename for this asset +/// The same asset will always lead to the same asset name +/// (Generated names do not include file extention.) +/proc/generate_asset_name(file) + return "asset.[md5(fcopy_rsc(file))]" //Converts an icon to base64. Operates by putting the icon in the iconCache savefile, // exporting it as text, and then parsing the base64 from that. @@ -1145,11 +1150,11 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 if (!isicon(I)) if (isfile(thing)) //special snowflake var/name = sanitize_filename("[generate_asset_name(thing)].png") - register_asset(name, thing) + SSassets.transport.register_asset(name, thing) for (var/mob/thing2 in targets) if(!istype(thing2) || !thing2.client) continue - send_asset(thing2?.client, key) + SSassets.transport.send_assets(thing2?.client, key) return "" var/atom/A = thing if (isnull(dir)) @@ -1171,13 +1176,13 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 I = icon(I, icon_state, dir, frame, moving) key = "[generate_asset_name(I)].png" - register_asset(key, I) + SSassets.transport.register_asset(key, I) for (var/mob/thing2 in targets) if(!istype(thing2) || !thing2.client) continue - send_asset(thing2?.client, key) + SSassets.transport.send_assets(thing2?.client, key) - return "" + return "" /proc/icon2base64html(thing) if (!thing) diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index d13cfc13066..047ad6274a1 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -561,6 +561,11 @@ else //regex everything else (works for /proc too) return lowertext(replacetext("[the_type]", "[type2parent(the_type)]/", "")) +/// Return html to load a url. +/// for use inside of browse() calls to html assets that might be loaded on a cdn. +/proc/url2htmlloader(url) + return {""} + /proc/strtohex(str) if(!istext(str)||!str) return diff --git a/code/_globalvars/special_traits.dm b/code/_globalvars/special_traits.dm index ffa2e4b2542..70395b24004 100644 --- a/code/_globalvars/special_traits.dm +++ b/code/_globalvars/special_traits.dm @@ -47,6 +47,8 @@ GLOBAL_LIST_INIT(special_traits, build_special_traits()) player = character.client apply_charflaw_equipment(character, player) apply_prefs_special(character, player) + if(player.prefs.loadout) + character.mind.special_items[player.prefs.loadout.name] = player.prefs.loadout.path /proc/apply_charflaw_equipment(mob/living/carbon/human/character, client/player) if(character.charflaw) diff --git a/code/_onclick/hud/mouseover.dm b/code/_onclick/hud/mouseover.dm index 4c96d34fc59..eee3ca5cc14 100644 --- a/code/_onclick/hud/mouseover.dm +++ b/code/_onclick/hud/mouseover.dm @@ -208,5 +208,5 @@ /client/proc/genmouseobj() mouseovertext = new /atom/movable/screen/movable/mouseover/maptext mouseoverbox = new /atom/movable/screen/movable/mouseover - var/datum/asset/stuff = get_asset_datum(/datum/asset/simple/roguefonts) + var/datum/asset/stuff = get_asset_datum(/datum/asset/simple/namespaced/roguefonts) stuff.send(src) diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 3ee46499080..f1e7a39b0a1 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -53,6 +53,9 @@ LoadPolicy() LoadChatFilter() + if(Master) + Master.OnConfigLoad() + /datum/controller/configuration/proc/full_wipe() if(IsAdminAdvancedProcCall()) return diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 9e6f527b45d..9d5cb28756c 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -623,3 +623,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate) else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount)) processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate) + +/datum/controller/master/proc/OnConfigLoad() + for (var/thing in subsystems) + var/datum/controller/subsystem/SS = thing + SS.OnConfigLoad() diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 233dd76709b..834f318f136 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -220,3 +220,6 @@ if ("queued_priority") //editing this breaks things. return 0 . = ..() + +/// Called after the config has been loaded or reloaded. +/datum/controller/subsystem/proc/OnConfigLoad() diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm index dfe6438d79f..9893502efc6 100644 --- a/code/controllers/subsystem/assets.dm +++ b/code/controllers/subsystem/assets.dm @@ -4,6 +4,21 @@ SUBSYSTEM_DEF(assets) flags = SS_NO_FIRE var/list/cache = list() var/list/preload = list() + var/datum/asset_transport/transport = new() + +/datum/controller/subsystem/assets/OnConfigLoad() + var/newtransporttype = /datum/asset_transport + switch (CONFIG_GET(string/asset_transport)) + if ("webroot") + newtransporttype = /datum/asset_transport/webroot + + if (newtransporttype == transport.type) + return + + var/datum/asset_transport/newtransport = new newtransporttype () + if (newtransport.validate_config()) + transport = newtransport + transport.Load() /datum/controller/subsystem/assets/Initialize(timeofday) for(var/type in typesof(/datum/asset)) @@ -11,8 +26,5 @@ SUBSYSTEM_DEF(assets) if (type != initial(A._abstract)) get_asset_datum(type) - preload = cache.Copy() //don't preload assets generated during the round - - for(var/client/C in GLOB.clients) - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(getFilesSlow), C, preload, FALSE), 10) - ..() + transport.Initialize(cache) + return ..() diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 0400fbf8698..37c8c4c85ad 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -8,12 +8,12 @@ var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id var/stylesheets[0] var/scripts[0] - var/title_image var/head_elements var/body_elements var/head_content = "" var/content = "" var/no_close_movement = FALSE + var/static/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common) /datum/browser/Destroy(force, ...) . = ..() @@ -45,7 +45,6 @@ if (nref) ref = nref RegisterSignal(ref, COMSIG_PARENT_QDELETING, PROC_REF(ref_deleted)) - add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs /datum/browser/proc/user_deleted(datum/source) SIGNAL_HANDLER @@ -61,9 +60,6 @@ /datum/browser/proc/set_window_options(nwindow_options) window_options = nwindow_options -/datum/browser/proc/set_title_image(ntitle_image) - title_image = ntitle_image - /datum/browser/proc/add_stylesheet(name, file) if (istype(name, /datum/asset/spritesheet)) var/datum/asset/spritesheet/sheet = name @@ -74,11 +70,11 @@ stylesheets[asset_name] = file if (!SSassets.cache[asset_name]) - register_asset(asset_name, file) + SSassets.transport.register_asset(asset_name, file) /datum/browser/proc/add_script(name, file) scripts["[ckey(name)].js"] = file - register_asset("[ckey(name)].js", file) + SSassets.transport.register_asset("[ckey(name)].js", file) /datum/browser/proc/set_content(ncontent) content = ncontent @@ -87,16 +83,14 @@ content += ncontent /datum/browser/proc/get_header() + var/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common) var/file + head_content += "" for (file in stylesheets) - head_content += "" + head_content += "" for (file in scripts) - head_content += "" - - var/title_attributes = "class='uiTitle'" - if (title_image) - title_attributes = "class='uiTitle icon' style='background-image: url([title_image]);'" + head_content += "" return {" @@ -107,7 +101,7 @@
- [title ? "
[title]
" : ""] + [title ? "
[title]
" : ""]
"} //" This is here because else the rest of the file looks like a string in notepad++. @@ -133,10 +127,11 @@ var/window_size = "" if (width && height) window_size = "size=[width]x[height];" + common_asset.send(user) if (stylesheets.len) - send_asset_list(user, stylesheets, verify=FALSE) + SSassets.transport.send_assets(user, stylesheets) if (scripts.len) - send_asset_list(user, scripts, verify=FALSE) + SSassets.transport.send_assets(user, scripts) user << browse(get_content(), "window=[window_id];[window_size][window_options]") if (use_onclose) setup_onclose() @@ -445,12 +440,6 @@ if (A.selectedbutton) return list("button" = A.selectedbutton, "settings" = A.settings) -// This will allow you to show an icon in the browse window -// This is added to mob so that it can be used without a reference to the browser object -// There is probably a better place for this... -/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1) - - // Registers the on-close verb for a browse window (client/verb/.windowclose) // this will be called when the close-button of a window is pressed. // diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index 8d724f567fb..8af31da4179 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -240,7 +240,6 @@ dat += "A [t].
" dat += "Which card will you remove next?" var/datum/browser/popup = new(user, "cardhand", "Hand of Cards", 400, 240) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) popup.set_content(dat) popup.open() diff --git a/code/game/world.dm b/code/game/world.dm index 8a2c123ea8b..cceec035bfd 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -39,8 +39,6 @@ GLOBAL_VAR(restart_counter) log_world("World loaded at [time_stamp()]!") - SetupExternalRSC() - GLOB.config_error_log = GLOB.world_manifest_log = GLOB.world_pda_log = GLOB.world_job_debug_log = GLOB.sql_error_log = GLOB.world_href_log = GLOB.world_runtime_log = GLOB.world_attack_log = GLOB.world_game_log = "data/logs/config_error.[GUID()].log" //temporary file used to record errors with loading config, moved to log directory once logging is set bl make_datum_references_lists() //initialises global lists for referencing frequently used datums (so that we only ever do it once) @@ -118,17 +116,6 @@ GLOBAL_VAR(restart_counter) #endif SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(addtimer), cb, 10 SECONDS)) -/world/proc/SetupExternalRSC() -#if (PRELOAD_RSC == 0) - GLOB.external_rsc_urls = world.file2list("[global.config.directory]/external_rsc_urls.txt","\n") - var/i=1 - while(i<=GLOB.external_rsc_urls.len) - if(GLOB.external_rsc_urls[i]) - i++ - else - GLOB.external_rsc_urls.Cut(i,i+1) -#endif - /world/proc/SetupLogs() var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] if(!override_dir) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 7dbdf5a43ee..a5b00e343c7 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -149,7 +149,8 @@ GLOBAL_PROTECT(admin_verbs_server) /client/proc/forcerandomrotate, /client/proc/adminchangemap, /client/proc/panicbunker, - /client/proc/toggle_hub + /client/proc/toggle_hub, + /client/proc/toggle_cdn ) GLOBAL_LIST_INIT(admin_verbs_debug, world.AVerbsDebug()) GLOBAL_PROTECT(admin_verbs_debug) diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm index f3ac048316d..4a59e0d3c29 100644 --- a/code/modules/admin/permissionedit.dm +++ b/code/modules/admin/permissionedit.dm @@ -9,7 +9,9 @@ /datum/admins/proc/edit_admin_permissions(action, target, operation, page) if(!check_rights(R_PERMISSIONS)) return - var/list/output = list("\[Permissions\]") + var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/group/permissions) + asset_cache_datum.send(usr) + var/list/output = list("\[Permissions\]") if(action) output += " | \[Log\] | \[Management\]
" else @@ -92,7 +94,7 @@ Permissions Panel - +
@@ -137,7 +139,7 @@ if(IsAdminAdvancedProcCall()) to_chat(usr, span_adminprefix("Admin Edit blocked: Advanced ProcCall detected.")) return - var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/permissions) + var/datum/asset/permissions_assets = get_asset_datum(/datum/asset/simple/namespaced/common) permissions_assets.send(src) var/admin_key = href_list["key"] var/admin_ckey = ckey(admin_key) diff --git a/code/modules/admin/sql_message_system.dm b/code/modules/admin/sql_message_system.dm index 9a43305e437..fce6c9d9973 100644 --- a/code/modules/admin/sql_message_system.dm +++ b/code/modules/admin/sql_message_system.dm @@ -502,7 +502,7 @@ alphatext = "filter: alpha(opacity=[alpha]); opacity: [alpha/100];" var/list/data = list("

") if(severity) - data += " " + data += " " data += "[timestamp] | [server] | [admin_key][secret ? " | - Secret" : ""]" if(expire_timestamp) data += " | Expires [expire_timestamp]" diff --git a/code/modules/admin/verbs/beakerpanel.dm b/code/modules/admin/verbs/beakerpanel.dm index ec690001561..9dd7f4e90ea 100644 --- a/code/modules/admin/verbs/beakerpanel.dm +++ b/code/modules/admin/verbs/beakerpanel.dm @@ -41,13 +41,16 @@ if(!check_rights()) return + var/datum/asset/asset_datum = get_asset_datum(/datum/asset/simple/namespaced/common) + asset_datum.send() + //Could somebody tell me why this isn't using the browser datum, given that it copypastes all of browser datum's html var/dat = {" - + diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index c8b67cf9672..32f53eb440b 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -70,3 +70,31 @@ load_admins() SSblackbox.record_feedback("tally", "admin_verb", 1, "Reload All Admins") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! message_admins("[key_name_admin(usr)] manually reloaded admins") + +/client/proc/toggle_cdn() + set name = "Toggle CDN" + set category = "Server" + var/static/admin_disabled_cdn_transport = null + if (alert(usr, "Are you sure you want to toggle the CDN asset transport?", "Confirm", "Yes", "No") != "Yes") + return + var/current_transport = CONFIG_GET(string/asset_transport) + if (!current_transport || current_transport == "simple") + if (admin_disabled_cdn_transport) + CONFIG_SET(string/asset_transport, admin_disabled_cdn_transport) + admin_disabled_cdn_transport = null + SSassets.OnConfigLoad() + message_admins("[key_name_admin(usr)] re-enabled the CDN asset transport") + log_admin("[key_name(usr)] re-enabled the CDN asset transport") + else + to_chat(usr, "The CDN is not enabled!") + if (alert(usr, "The CDN asset transport is not enabled! If you having issues with assets you can also try disabling filename mutations.", "The CDN asset transport is not enabled!", "Try disabling filename mutations", "Nevermind") == "Try disabling filename mutations") + SSassets.transport.dont_mutate_filenames = !SSassets.transport.dont_mutate_filenames + message_admins("[key_name_admin(usr)] [(SSassets.transport.dont_mutate_filenames ? "disabled" : "re-enabled")] asset filename transforms") + log_admin("[key_name(usr)] [(SSassets.transport.dont_mutate_filenames ? "disabled" : "re-enabled")] asset filename transforms") + else + admin_disabled_cdn_transport = current_transport + CONFIG_SET(string/asset_transport, "simple") + SSassets.OnConfigLoad() + SSassets.transport.dont_mutate_filenames = TRUE + message_admins("[key_name_admin(usr)] disabled the CDN asset transport") + log_admin("[key_name(usr)] disabled the CDN asset transport") diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index a8f792aa905..7a42940dab6 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -231,6 +231,7 @@ GLOBAL_LIST_INIT(ambience_files, list( 'sound/music/area/bog.ogg', 'sound/music/area/caves.ogg', 'sound/music/area/church.ogg', + 'sound/music/area/13angels.ogg', 'sound/music/area/dwarf.ogg', 'sound/music/area/field.ogg', 'sound/music/area/magiciantower.ogg', diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index 00c5fbe89f9..01ae0ec8aba 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -4,8 +4,8 @@ //set src in world var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round. - if(!usr.client || !usr.client.holder) //This is usr because admins can call the proc on other clients, even if they're not admins, to show them VVs. - to_chat(usr, span_danger("I need to be an administrator to access this.")) + if(!usr.client || !usr.client.holder) //This is usr because admins can call the proc on other clients, even if they're not admins, to show them VVs. + to_chat(usr, span_danger("You need to be an administrator to access this.")) return if(!D) @@ -39,6 +39,8 @@ sprite_text = no_icon? "\[NO ICON\]" : "

" dat += "\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"
" var/list/header = islist(D)? list("/list") : D.vv_get_header() + var/ref_line = "@[copytext(refid, 2, -1)]" // get rid of the brackets, add a @ prefix for copy pasting in asay + var/marked_line if(holder && holder.marked_datum && holder.marked_datum == D) marked_line = VV_MSG_MARKED @@ -72,7 +74,7 @@ if(!islist) for(var/V in D.vars) names += V - sleep(1) + sleep(1 TICKS) var/list/variable_html = list() if(islist) @@ -92,17 +94,9 @@ var/html = {" + [title] - + "}, "window=asset_cache_browser&file=asset_cache_send_verify.htm") + else + src << browse({""}, "window=asset_cache_browser&file=asset_cache_send_verify.htm") + + while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic() + stoplag(1) // Lock up the caller until this is received. + t++ + if (t < timeout_time) + return TRUE diff --git a/code/modules/asset_cache/asset_configs.dm b/code/modules/asset_cache/asset_configs.dm new file mode 100644 index 00000000000..c839ccc078d --- /dev/null +++ b/code/modules/asset_cache/asset_configs.dm @@ -0,0 +1,30 @@ +/datum/config_entry/keyed_list/external_rsc_urls + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/flag/asset_simple_preload + +/datum/config_entry/string/asset_transport +/datum/config_entry/string/asset_transport/ValidateAndSet(str_val) + return (lowertext(str_val) in list("simple", "webroot")) && ..(lowertext(str_val)) + +/datum/config_entry/string/asset_cdn_webroot + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/asset_cdn_webroot/ValidateAndSet(str_var) + if (!str_var || trim(str_var) == "") + return FALSE + if (str_var && str_var[length(str_var)] != "/") + str_var += "/" + return ..(str_var) + +/datum/config_entry/string/asset_cdn_url + protection = CONFIG_ENTRY_LOCKED + default = null + +/datum/config_entry/string/asset_cdn_url/ValidateAndSet(str_var) + if (!str_var || trim(str_var) == "") + return FALSE + if (str_var && str_var[length(str_var)] != "/") + str_var += "/" + return ..(str_var) diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm new file mode 100644 index 00000000000..82456abb107 --- /dev/null +++ b/code/modules/asset_cache/asset_list.dm @@ -0,0 +1,307 @@ + +//These datums are used to populate the asset cache, the proc "register()" does this. +//Place any asset datums you create in asset_list_items.dm + +//all of our asset datums, used for referring to these later +GLOBAL_LIST_EMPTY(asset_datums) + +//get an assetdatum or make a new one +/proc/get_asset_datum(type) + return GLOB.asset_datums[type] || new type() + +/datum/asset + var/_abstract = /datum/asset + +/datum/asset/New() + GLOB.asset_datums[type] = src + register() + +/datum/asset/proc/get_url_mappings() + return list() + +/datum/asset/proc/register() + return + +/datum/asset/proc/send(client) + return + + +//If you don't need anything complicated. +/datum/asset/simple + _abstract = /datum/asset/simple + var/assets = list() //! list of assets for this datum in the form of asset_filename = asset_file. At runtime the asset_file will be converted into a asset_cache datum. + var/legacy = FALSE //! set to true to have this asset also be sent via browse_rsc when cdn asset transports are enabled. + +/datum/asset/simple/register() + for(var/asset_name in assets) + var/datum/asset_cache_item/ACI = SSassets.transport.register_asset(asset_name, assets[asset_name]) + if (!ACI) + log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]") + continue + if (legacy) + ACI.legacy = TRUE + assets[asset_name] = ACI + +/datum/asset/simple/send(client) + . = SSassets.transport.send_assets(client, assets) + +/datum/asset/simple/get_url_mappings() + . = list() + for (var/asset_name in assets) + .[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name]) + + +// For registering or sending multiple others at once +/datum/asset/group + _abstract = /datum/asset/group + var/list/children + +/datum/asset/group/register() + for(var/type in children) + get_asset_datum(type) + +/datum/asset/group/send(client/C) + for(var/type in children) + var/datum/asset/A = get_asset_datum(type) + . = A.send(C) || . + +/datum/asset/group/get_url_mappings() + . = list() + for(var/type in children) + var/datum/asset/A = get_asset_datum(type) + . += A.get_url_mappings() + +// spritesheet implementation - coalesces various icons into a single .png file +// and uses CSS to select icons out of that file - saves on transferring some +// 1400-odd individual PNG files +#define SPR_SIZE 1 +#define SPR_IDX 2 +#define SPRSZ_COUNT 1 +#define SPRSZ_ICON 2 +#define SPRSZ_STRIPPED 3 + +/datum/asset/spritesheet + _abstract = /datum/asset/spritesheet + var/name + var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped) + var/list/sprites = list() // "foo_bar" -> list("32x32", 5) + +/datum/asset/spritesheet/register() + if (!name) + CRASH("spritesheet [type] cannot register without a name") + ensure_stripped() + for(var/size_id in sizes) + var/size = sizes[size_id] + SSassets.transport.register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED]) + var/res_name = "spritesheet_[name].css" + var/fname = "data/spritesheets/[res_name]" + fdel(fname) + text2file(generate_css(), fname) + SSassets.transport.register_asset(res_name, fcopy_rsc(fname)) + fdel(fname) + +/datum/asset/spritesheet/send(client/C) + if (!name) + return + var/all = list("spritesheet_[name].css") + for(var/size_id in sizes) + all += "[name]_[size_id].png" + . = SSassets.transport.send_assets(C, all) + +/datum/asset/spritesheet/get_url_mappings() + if (!name) + return + . = list("spritesheet_[name].css" = SSassets.transport.get_asset_url("spritesheet_[name].css")) + for(var/size_id in sizes) + .["[name]_[size_id].png"] = SSassets.transport.get_asset_url("[name]_[size_id].png") + + + +/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes) + for(var/size_id in sizes_to_strip) + var/size = sizes[size_id] + if (size[SPRSZ_STRIPPED]) + continue + + // save flattened version + var/fname = "data/spritesheets/[name]_[size_id].png" + fcopy(size[SPRSZ_ICON], fname) + var/error = rustg_dmi_strip_metadata(fname) + if(length(error)) + stack_trace("Failed to strip [name]_[size_id].png: [error]") + size[SPRSZ_STRIPPED] = icon(fname) + fdel(fname) + +/datum/asset/spritesheet/proc/generate_css() + var/list/out = list() + + for (var/size_id in sizes) + var/size = sizes[size_id] + var/icon/tiny = size[SPRSZ_ICON] + out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[SSassets.transport.get_asset_url("[name]_[size_id].png")]') no-repeat;}" + + for (var/sprite_id in sprites) + var/sprite = sprites[sprite_id] + var/size_id = sprite[SPR_SIZE] + var/idx = sprite[SPR_IDX] + var/size = sizes[size_id] + + var/icon/tiny = size[SPRSZ_ICON] + var/icon/big = size[SPRSZ_STRIPPED] + var/per_line = big.Width() / tiny.Width() + var/x = (idx % per_line) * tiny.Width() + var/y = round(idx / per_line) * tiny.Height() + + out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}" + + return out.Join("\n") + +/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE) + I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving) + if (!I || !length(icon_states(I))) // that direction or state doesn't exist + return + var/size_id = "[I.Width()]x[I.Height()]" + var/size = sizes[size_id] + + if (sprites[sprite_name]) + CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])") + + if (size) + var/position = size[SPRSZ_COUNT]++ + var/icon/sheet = size[SPRSZ_ICON] + size[SPRSZ_STRIPPED] = null + sheet.Insert(I, icon_state=sprite_name) + sprites[sprite_name] = list(size_id, position) + else + sizes[size_id] = size = list(1, I, null) + sprites[sprite_name] = list(size_id, 0) + +/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions) + if (length(prefix)) + prefix = "[prefix]-" + + if (!directions) + directions = list(SOUTH) + + for (var/icon_state_name in icon_states(I)) + for (var/direction in directions) + var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : "" + Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction) + +/datum/asset/spritesheet/proc/css_tag() + return {""} + +/datum/asset/spritesheet/proc/css_filename() + return SSassets.transport.get_asset_url("spritesheet_[name].css") + +/datum/asset/spritesheet/proc/icon_tag(sprite_name) + var/sprite = sprites[sprite_name] + if (!sprite) + return null + var/size_id = sprite[SPR_SIZE] + return {""} + +/datum/asset/spritesheet/proc/icon_class_name(sprite_name) + var/sprite = sprites[sprite_name] + if (!sprite) + return null + var/size_id = sprite[SPR_SIZE] + return {"[name][size_id] [sprite_name]"} + +#undef SPR_SIZE +#undef SPR_IDX +#undef SPRSZ_COUNT +#undef SPRSZ_ICON +#undef SPRSZ_STRIPPED + + +/datum/asset/spritesheet/simple + _abstract = /datum/asset/spritesheet/simple + var/list/assets + +/datum/asset/spritesheet/simple/register() + for (var/key in assets) + Insert(key, assets[key]) + ..() + +//Generates assets based on iconstates of a single icon +/datum/asset/simple/icon_states + _abstract = /datum/asset/simple/icon_states + var/icon + var/list/directions = list(SOUTH) + var/frame = 1 + var/movement_states = FALSE + + var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png" + var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format + +/datum/asset/simple/icon_states/register(_icon = icon) + for(var/icon_state_name in icon_states(_icon)) + for(var/direction in directions) + var/asset = icon(_icon, icon_state_name, direction, frame, movement_states) + if (!asset) + continue + asset = fcopy_rsc(asset) //dedupe + var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : "" + var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png") + if (generic_icon_names) + asset_name = "[generate_asset_name(asset)].png" + + SSassets.transport.register_asset(asset_name, asset) + +/datum/asset/simple/icon_states/multiple_icons + _abstract = /datum/asset/simple/icon_states/multiple_icons + var/list/icons + +/datum/asset/simple/icon_states/multiple_icons/register() + for(var/i in icons) + ..(i) + +/// Namespace'ed assets (for static css and html files) +/// When sent over a cdn transport, all assets in the same asset datum will exist in the same folder, as their plain names. +/// Used to ensure css files can reference files by url() without having to generate the css at runtime, both the css file and the files it depends on must exist in the same namespace asset datum. (Also works for html) +/// For example `blah.css` with asset `blah.png` will get loaded as `namespaces/a3d..14f/f12..d3c.css` and `namespaces/a3d..14f/blah.png`. allowing the css file to load `blah.png` by a relative url rather then compute the generated url with get_url_mappings(). +/// The namespace folder's name will change if any of the assets change. (excluding parent assets) +/datum/asset/simple/namespaced + _abstract = /datum/asset/simple/namespaced + /// parents - list of the parent asset or assets (in name = file assoicated format) for this namespace. + /// parent assets must be referenced by their generated url, but if an update changes a parent asset, it won't change the namespace's identity. + var/list/parents = list() + +/datum/asset/simple/namespaced/register() + if (legacy) + assets |= parents + var/list/hashlist = list() + var/list/sorted_assets = sortList(assets) + + for (var/asset_name in sorted_assets) + var/datum/asset_cache_item/ACI = new(asset_name, sorted_assets[asset_name]) + if (!ACI?.hash) + log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]") + continue + hashlist += ACI.hash + sorted_assets[asset_name] = ACI + var/namespace = md5(hashlist.Join()) + + for (var/asset_name in parents) + var/datum/asset_cache_item/ACI = new(asset_name, parents[asset_name]) + if (!ACI?.hash) + log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]") + continue + ACI.namespace_parent = TRUE + sorted_assets[asset_name] = ACI + + for (var/asset_name in sorted_assets) + var/datum/asset_cache_item/ACI = sorted_assets[asset_name] + if (!ACI?.hash) + log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]") + continue + ACI.namespace = namespace + + assets = sorted_assets + ..() + +/// Get a html string that will load a html asset. +/// Needed because byond doesn't allow you to browse() to a url. +/datum/asset/simple/namespaced/proc/get_htmlloader(filename) + return url2htmlloader(SSassets.transport.get_asset_url(filename, assets[filename])) diff --git a/code/modules/asset_cache/readme.md b/code/modules/asset_cache/readme.md new file mode 100644 index 00000000000..c8c9d78b719 --- /dev/null +++ b/code/modules/asset_cache/readme.md @@ -0,0 +1,37 @@ +# Asset cache system + +## Framework for managing browser assets (javascript,css,images,etc) + +This manages getting the asset to the client without doing unneeded re-sends, as well as utilizing any configured cdns. + +There are two frameworks for using this system: + +### Asset datum: + +Make a datum in asset_list_items.dm with your browser assets for your thing. + +Checkout asset_list.dm for the helper subclasses + +The `simple` subclass will most likely be of use for most cases. + +Call get_asset_datum() with the type of the datum you created to get your asset cache datum + +Call .send(client|usr) on that datum to send the asset to the client. Depending on the asset transport this may or may not block. + +Call .get_url_mappings() to get an associated list with the urls your assets can be found at. + +### Manual backend: + +See the documentation for `/datum/asset_transport` for the backend api the asset datums utilize. + +The global variable `SSassets.transport` contains the currently configured transport. + + + +### Notes: + +Because byond browse() calls use non-blocking queues, if your code uses output() (which bypasses all of these queues) to invoke javascript functions you will need to first have the javascript announce to the server it has loaded before trying to invoke js functions. + +To make your code work with any CDNs configured by the server, you must make sure assets are referenced from the url returned by `get_url_mappings()` or by asset_transport's `get_asset_url()`. (TGUI also has helpers for this.) If this can not be easily done, you can bypass the cdn using legacy assets, see the simple asset datum for details. + +CSS files that use url() can be made to use the CDN without needing to rewrite all url() calls in code by using the namespaced helper datum. See the documentation for `/datum/asset/simple/namespaced` for details. diff --git a/code/modules/asset_cache/transport/asset_transport.dm b/code/modules/asset_cache/transport/asset_transport.dm new file mode 100644 index 00000000000..abebf1f39a2 --- /dev/null +++ b/code/modules/asset_cache/transport/asset_transport.dm @@ -0,0 +1,142 @@ +/// When sending mutiple assets, how many before we give the client a quaint little sending resources message +#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8 + +/// Base browse_rsc asset transport +/datum/asset_transport + var/name = "Simple browse_rsc asset transport" + var/static/list/preload + /// Don't mutate the filename of assets when sending via browse_rsc. + /// This is to make it easier to debug issues with assets, and allow server operators to bypass issues that make it to production. + /// If turning this on fixes asset issues, something isn't using get_asset_url and the asset isn't marked legacy, fix one of those. + var/dont_mutate_filenames = FALSE + +/// Called when the transport is loaded by the config controller, not called on the default transport unless it gets loaded by a config change. +/datum/asset_transport/proc/Load() + if (CONFIG_GET(flag/asset_simple_preload)) + for(var/client/C in GLOB.clients) + addtimer(CALLBACK(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS) + +/// Initialize - Called when SSassets initializes. +/datum/asset_transport/proc/Initialize(list/assets) + preload = assets.Copy() + if (!CONFIG_GET(flag/asset_simple_preload)) + return + for(var/client/C in GLOB.clients) + addtimer(CALLBACK(src, PROC_REF(send_assets_slow), C, preload), 1 SECONDS) + + +/// Register a browser asset with the asset cache system +/// asset_name - the identifier of the asset +/// asset - the actual asset file (or an asset_cache_item datum) +/// returns a /datum/asset_cache_item. +/// mutiple calls to register the same asset under the same asset_name return the same datum +/datum/asset_transport/proc/register_asset(asset_name, asset) + var/datum/asset_cache_item/ACI = asset + if (!istype(ACI)) + ACI = new(asset_name, asset) + if (!ACI || !ACI.hash) + CRASH("ERROR: Invalid asset: [asset_name]:[asset]:[ACI]") + if (SSassets.cache[asset_name]) + var/datum/asset_cache_item/OACI = SSassets.cache[asset_name] + OACI.legacy = ACI.legacy = (ACI.legacy|OACI.legacy) + OACI.namespace_parent = ACI.namespace_parent = (ACI.namespace_parent | OACI.namespace_parent) + OACI.namespace = OACI.namespace || ACI.namespace + if (OACI.hash != ACI.hash) + var/error_msg = "ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset hash: [OACI.hash] new asset hash:[ACI.hash]" + stack_trace(error_msg) + log_asset(error_msg) + else + if (length(ACI.namespace)) + return ACI + return OACI + + SSassets.cache[asset_name] = ACI + return ACI + + +/// Returns a url for a given asset. +/// asset_name - Name of the asset. +/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name +/datum/asset_transport/proc/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item) + if (!istype(asset_cache_item)) + asset_cache_item = SSassets.cache[asset_name] + if (dont_mutate_filenames || asset_cache_item.legacy || (asset_cache_item.namespace && !asset_cache_item.namespace_parent)) // to ensure code that breaks on cdns breaks in local testing, we only use the normal filename on legacy assets and name space assets. + return url_encode(asset_cache_item.name) + return url_encode("asset.[asset_cache_item.hash][asset_cache_item.ext]") + + +/// Sends a list of browser assets to a client +/// client - a client or mob +/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum. +/// Returns TRUE if any assets were sent. +/datum/asset_transport/proc/send_assets(client/client, list/asset_list) + if(isnull(client)) + return + if (!istype(client)) + if (ismob(client)) + var/mob/M = client + if (M.client) + client = M.client + else //no stacktrace because this will mainly happen because the client went away + return + else + CRASH("Invalid argument: client: `[client]`") + if (!islist(asset_list)) + asset_list = list(asset_list) + var/list/unreceived = list() + + for (var/asset_name in asset_list) + var/datum/asset_cache_item/ACI = asset_list[asset_name] + if (!istype(ACI) && !(ACI = SSassets.cache[asset_name])) + log_asset("ERROR: can't send asset `[asset_name]`: unregistered or invalid state: `[ACI]`") + continue + var/asset_file = ACI.resource + if (!asset_file) + log_asset("ERROR: can't send asset `[asset_name]`: invalid registered resource: `[ACI.resource]`") + continue + + var/asset_hash = ACI.hash + var/new_asset_name = asset_name + if (!dont_mutate_filenames && !ACI.legacy && (!ACI.namespace || ACI.namespace_parent)) + new_asset_name = "asset.[ACI.hash][ACI.ext]" + if (client.sent_assets[new_asset_name] == asset_hash) + if (GLOB.Debug2) + log_asset("DEBUG: Skipping send of `[asset_name]` (as `[new_asset_name]`) for `[client]` because it already exists in the client's sent_assets list") + continue + unreceived[asset_name] = ACI + + if (unreceived.len) + if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT) + to_chat(client, "Sending Resources...") + + for (var/asset_name in unreceived) + var/new_asset_name = asset_name + var/datum/asset_cache_item/ACI = unreceived[asset_name] + if (!dont_mutate_filenames && !ACI.legacy && (!ACI.namespace || ACI.namespace_parent)) + new_asset_name = "asset.[ACI.hash][ACI.ext]" + log_asset("Sending asset `[asset_name]` to client `[client]` as `[new_asset_name]`") + client << browse_rsc(ACI.resource, new_asset_name) + + client.sent_assets[new_asset_name] = ACI.hash + + addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + return TRUE + return FALSE + + +/// Precache files without clogging up the browse() queue, used for passively sending files on connection start. +/datum/asset_transport/proc/send_assets_slow(client/client, list/files, filerate = 3) + var/startingfilerate = filerate + for (var/file in files) + if (!client) + break + if (send_assets(client, file)) + if (!(--filerate)) + filerate = startingfilerate + client.browse_queue_flush() + stoplag(0) //queuing calls like this too quickly can cause issues in some client versions + +/// Check the config is valid to load this transport +/// Returns TRUE or FALSE +/datum/asset_transport/proc/validate_config(log = TRUE) + return TRUE diff --git a/code/modules/asset_cache/transport/webroot.dm b/code/modules/asset_cache/transport/webroot.dm new file mode 100644 index 00000000000..8ff734460df --- /dev/null +++ b/code/modules/asset_cache/transport/webroot.dm @@ -0,0 +1,87 @@ +/// CDN Webroot asset transport. +/datum/asset_transport/webroot + name = "CDN Webroot asset transport" + +/datum/asset_transport/webroot/Load() + if (validate_config(log = FALSE)) + load_existing_assets() + +/// Processes thru any assets that were registered before we were loaded as a transport. +/datum/asset_transport/webroot/proc/load_existing_assets() + for (var/asset_name in SSassets.cache) + var/datum/asset_cache_item/ACI = SSassets.cache[asset_name] + save_asset_to_webroot(ACI) + +/// Register a browser asset with the asset cache system +/// We also save it to the CDN webroot at this step instead of waiting for send_assets() +/// asset_name - the identifier of the asset +/// asset - the actual asset file or an asset_cache_item datum. +/datum/asset_transport/webroot/register_asset(asset_name, asset) + . = ..() + var/datum/asset_cache_item/ACI = . + + if (istype(ACI) && ACI.hash) + save_asset_to_webroot(ACI) + +/// Saves the asset to the webroot taking into account namespaces and hashes. +/datum/asset_transport/webroot/proc/save_asset_to_webroot(datum/asset_cache_item/ACI) + var/webroot = CONFIG_GET(string/asset_cdn_webroot) + var/newpath = "[webroot][get_asset_suffex(ACI)]" + if (fexists(newpath)) + return + if (fexists("[newpath].gz")) //its a common pattern in webhosting to save gzip'ed versions of text files and let the webserver serve them up as gzip compressed normal files, sometimes without keeping the original version. + return + return fcopy(ACI.resource, newpath) + +/// Returns a url for a given asset. +/// asset_name - Name of the asset. +/// asset_cache_item - asset cache item datum for the asset, optional, overrides asset_name +/datum/asset_transport/webroot/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item) + if (!istype(asset_cache_item)) + asset_cache_item = SSassets.cache[asset_name] + var/url = CONFIG_GET(string/asset_cdn_url) //config loading will handle making sure this ends in a / + return "[url][get_asset_suffex(asset_cache_item)]" + +/datum/asset_transport/webroot/proc/get_asset_suffex(datum/asset_cache_item/asset_cache_item) + var/base = "" + var/filename = "asset.[asset_cache_item.hash][asset_cache_item.ext]" + if (length(asset_cache_item.namespace)) + base = "namespaces/[asset_cache_item.namespace]/" + if (!asset_cache_item.namespace_parent) + filename = "[asset_cache_item.name]" + return base + filename + + +/// webroot asset sending - does nothing unless passed legacy assets +/datum/asset_transport/webroot/send_assets(client/client, list/asset_list) + . = FALSE + var/list/legacy_assets = list() + if (!islist(asset_list)) + asset_list = list(asset_list) + for (var/asset_name in asset_list) + var/datum/asset_cache_item/ACI = asset_list[asset_name] + if (!istype(ACI)) + ACI = SSassets.cache[asset_name] + if (!ACI) + legacy_assets += asset_name //pass it on to base send_assets so it can output an error + continue + if (ACI.legacy) + legacy_assets[asset_name] = ACI + if (length(legacy_assets)) + . = ..(client, legacy_assets) + + +/// webroot slow asset sending - does nothing. +/datum/asset_transport/webroot/send_assets_slow(client/client, list/files, filerate) + return FALSE + +/datum/asset_transport/webroot/validate_config(log = TRUE) + if (!CONFIG_GET(string/asset_cdn_url)) + if (log) + log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_URL") + return FALSE + if (!CONFIG_GET(string/asset_cdn_webroot)) + if (log) + log_asset("ERROR: [type]: Invalid Config: ASSET_CDN_WEBROOT") + return FALSE + return TRUE diff --git a/code/modules/asset_cache/validate_assets.html b/code/modules/asset_cache/validate_assets.html new file mode 100644 index 00000000000..70fdca8a9d7 --- /dev/null +++ b/code/modules/asset_cache/validate_assets.html @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm deleted file mode 100644 index dde8816ebb2..00000000000 --- a/code/modules/client/asset_cache.dm +++ /dev/null @@ -1,795 +0,0 @@ -/* -Asset cache quick users guide: - -Make a datum at the bottom of this file with your assets for your thing. -The simple subsystem will most like be of use for most cases. -Then call get_asset_datum() with the type of the datum you created and store the return -Then call .send(client) on that stored return value. - -You can set verify to TRUE if you want send() to sleep until the client has the assets. -*/ - - -// Amount of time(ds) MAX to send per asset, if this get exceeded we cancel the sleeping. -// This is doubled for the first asset, then added per asset after -#define ASSET_CACHE_SEND_TIMEOUT 7 - -//When sending mutiple assets, how many before we give the client a quaint little sending resources message -#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8 - -//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files -#define ASSET_CACHE_PRELOAD_CONCURRENT 3 - -/client - var/list/cache = list() // List of all assets sent to this client by the asset cache. - var/list/completed_asset_jobs = list() // List of all completed jobs, awaiting acknowledgement. - var/list/sending = list() - var/last_asset_job = 0 // Last job done. - -//This proc sends the asset to the client, but only if it needs it. -//This proc blocks(sleeps) unless verify is set to false -/proc/send_asset(client/client, asset_name, verify = TRUE) - if(!istype(client)) - if(ismob(client)) - var/mob/M = client - if(M.client) - client = M.client - - else - return 0 - - else - return 0 - - if(client.cache.Find(asset_name) || client.sending.Find(asset_name)) - return 0 - - log_asset("Sending asset [asset_name] to client [client]") - client << browse_rsc(SSassets.cache[asset_name], asset_name) - if(!verify) - client.cache += asset_name - return 1 - - client.sending |= asset_name - var/job = ++client.last_asset_job - - client << browse({" - - "}, "window=asset_cache_browser") - - var/t = 0 - var/timeout_time = (ASSET_CACHE_SEND_TIMEOUT * client.sending.len) + ASSET_CACHE_SEND_TIMEOUT - while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic() - stoplag(1) // Lock up the caller until this is received. - t++ - - if(client) - client.sending -= asset_name - client.cache |= asset_name - client.completed_asset_jobs -= job - - return 1 - -//This proc blocks(sleeps) unless verify is set to false -/proc/send_asset_list(client/client, list/asset_list, verify = TRUE) - if(!istype(client)) - if(ismob(client)) - var/mob/M = client - if(M.client) - client = M.client - - else - return 0 - - else - return 0 - - var/list/unreceived = asset_list - (client.cache + client.sending) - if(!unreceived || !unreceived.len) - return 0 -// if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT) -// to_chat(client, "Sending Resources...") - for(var/asset in unreceived) - if (asset in SSassets.cache) - log_asset("Sending asset [asset] to client [client]") - client << browse_rsc(SSassets.cache[asset], asset) - - if(!verify) // Can't access the asset cache browser, rip. - client.cache += unreceived - return 1 - - client.sending |= unreceived - var/job = ++client.last_asset_job - - client << browse({" - - "}, "window=asset_cache_browser") - - var/t = 0 - var/timeout_time = ASSET_CACHE_SEND_TIMEOUT * client.sending.len - while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic() - stoplag(1) // Lock up the caller until this is received. - t++ - - if(client) - client.sending -= unreceived - client.cache |= unreceived - client.completed_asset_jobs -= job - - return 1 - -//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start. -//The proc calls procs that sleep for long times. -/proc/getFilesSlow(client/client, list/files, register_asset = TRUE) - var/concurrent_tracker = 1 - for(var/file in files) - if (!client) - break - if (register_asset) - register_asset(file, files[file]) - if (concurrent_tracker >= ASSET_CACHE_PRELOAD_CONCURRENT) - concurrent_tracker = 1 - send_asset(client, file) - else - concurrent_tracker++ - send_asset(client, file, verify=FALSE) - - stoplag(0) //queuing calls like this too quickly can cause issues in some client versions - -//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up. -//if it's an icon or something be careful, you'll have to copy it before further use. -/proc/register_asset(asset_name, asset) - SSassets.cache[asset_name] = asset - -//Generated names do not include file extention. -//Used mainly for code that deals with assets in a generic way -//The same asset will always lead to the same asset name -/proc/generate_asset_name(file) - return "asset.[md5(fcopy_rsc(file))]" - - -//These datums are used to populate the asset cache, the proc "register()" does this. - -//all of our asset datums, used for referring to these later -GLOBAL_LIST_EMPTY(asset_datums) - -//get an assetdatum or make a new one -/proc/get_asset_datum(type) - return GLOB.asset_datums[type] || new type() - -/datum/asset - var/_abstract = /datum/asset - -/datum/asset/New() - GLOB.asset_datums[type] = src - register() - -/datum/asset/proc/register() - return - -/datum/asset/proc/send(client) - return - - -//If you don't need anything complicated. -/datum/asset/simple - _abstract = /datum/asset/simple - var/assets = list() - var/verify = FALSE - -/datum/asset/simple/register() - for(var/asset_name in assets) - register_asset(asset_name, assets[asset_name]) - -/datum/asset/simple/send(client) - send_asset_list(client,assets,verify) - - -// For registering or sending multiple others at once -/datum/asset/group - _abstract = /datum/asset/group - var/list/children - -/datum/asset/group/register() - for(var/type in children) - get_asset_datum(type) - -/datum/asset/group/send(client/C) - for(var/type in children) - var/datum/asset/A = get_asset_datum(type) - A.send(C) - - -// spritesheet implementation - coalesces various icons into a single .png file -// and uses CSS to select icons out of that file - saves on transferring some -// 1400-odd individual PNG files -#define SPR_SIZE 1 -#define SPR_IDX 2 -#define SPRSZ_COUNT 1 -#define SPRSZ_ICON 2 -#define SPRSZ_STRIPPED 3 - -/datum/asset/spritesheet - _abstract = /datum/asset/spritesheet - var/name - var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped) - var/list/sprites = list() // "foo_bar" -> list("32x32", 5) - var/verify = FALSE - -/datum/asset/spritesheet/register() - if (!name) - CRASH("spritesheet [type] cannot register without a name") - ensure_stripped() - - var/res_name = "test.css" - var/fname = "data/spritesheets/[res_name]" - fdel(fname) - text2file(generate_css(), fname) - register_asset(res_name, fcopy_rsc(fname)) - fdel(fname) - - for(var/size_id in sizes) - var/size = sizes[size_id] - register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED]) - -/datum/asset/spritesheet/send(client/C) - if (!name) - return - var/all = list("spritesheet_[name].css") - for(var/size_id in sizes) - all += "[name]_[size_id].png" - send_asset_list(C, all, verify) - -/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes) - for(var/size_id in sizes_to_strip) - var/size = sizes[size_id] - if (size[SPRSZ_STRIPPED]) - continue - - // save flattened version - var/fname = "data/spritesheets/[name]_[size_id].png" - fcopy(size[SPRSZ_ICON], fname) - var/error = rustg_dmi_strip_metadata(fname) - if(length(error)) - stack_trace("Failed to strip [name]_[size_id].png: [error]") - size[SPRSZ_STRIPPED] = icon(fname) - fdel(fname) - -/datum/asset/spritesheet/proc/generate_css() - var/list/out = list() - - for (var/size_id in sizes) - var/size = sizes[size_id] - var/icon/tiny = size[SPRSZ_ICON] - out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}" - - for (var/sprite_id in sprites) - var/sprite = sprites[sprite_id] - var/size_id = sprite[SPR_SIZE] - var/idx = sprite[SPR_IDX] - var/size = sizes[size_id] - - var/icon/tiny = size[SPRSZ_ICON] - var/icon/big = size[SPRSZ_STRIPPED] - var/per_line = big.Width() / tiny.Width() - var/x = (idx % per_line) * tiny.Width() - var/y = round(idx / per_line) * tiny.Height() - - out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}" - - return out.Join("\n") - -/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE) - I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving) - if (!I || !length(icon_states(I))) // that direction or state doesn't exist - return - var/size_id = "[I.Width()]x[I.Height()]" - var/size = sizes[size_id] - - if (sprites[sprite_name]) - CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])") - - if (size) - var/position = size[SPRSZ_COUNT]++ - var/icon/sheet = size[SPRSZ_ICON] - size[SPRSZ_STRIPPED] = null - sheet.Insert(I, icon_state=sprite_name) - sprites[sprite_name] = list(size_id, position) - else - sizes[size_id] = size = list(1, I, null) - sprites[sprite_name] = list(size_id, 0) - -/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions) - if (length(prefix)) - prefix = "[prefix]-" - - if (!directions) - directions = list(SOUTH) - - for (var/icon_state_name in icon_states(I)) - for (var/direction in directions) - var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : "" - Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction) - -/datum/asset/spritesheet/proc/css_tag() - return {""} - -/datum/asset/spritesheet/proc/icon_tag(sprite_name) - var/sprite = sprites[sprite_name] - if (!sprite) - return null - var/size_id = sprite[SPR_SIZE] - return {""} - -/datum/asset/spritesheet/proc/icon_class_name(sprite_name) - var/sprite = sprites[sprite_name] - if (!sprite) - return null - var/size_id = sprite[SPR_SIZE] - return {"[name][size_id] [sprite_name]"} - -#undef SPR_SIZE -#undef SPR_IDX -#undef SPRSZ_COUNT -#undef SPRSZ_ICON -#undef SPRSZ_STRIPPED - - -/datum/asset/spritesheet/simple - _abstract = /datum/asset/spritesheet/simple - var/list/assets - -/datum/asset/spritesheet/simple/register() - for (var/key in assets) - Insert(key, assets[key]) - ..() - -//Generates assets based on iconstates of a single icon -/datum/asset/simple/icon_states - _abstract = /datum/asset/simple/icon_states - var/icon - var/list/directions = list(SOUTH) - var/frame = 1 - var/movement_states = FALSE - - var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png" - var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format - - verify = FALSE - -/datum/asset/simple/icon_states/register(_icon = icon) - for(var/icon_state_name in icon_states(_icon)) - for(var/direction in directions) - var/asset = icon(_icon, icon_state_name, direction, frame, movement_states) - if (!asset) - continue - asset = fcopy_rsc(asset) //dedupe - var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : "" - var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png") - if (generic_icon_names) - asset_name = "[generate_asset_name(asset)].png" - - register_asset(asset_name, asset) - -/datum/asset/simple/icon_states/multiple_icons - _abstract = /datum/asset/simple/icon_states/multiple_icons - var/list/icons - -/datum/asset/simple/icon_states/multiple_icons/register() - for(var/i in icons) - ..(i) - - -//DEFINITIONS FOR ASSET DATUMS START HERE. - -/datum/asset/simple/tgui -/* assets = list( - // tgui - "tgui.css" = 'tgui/assets/tgui.css', - "tgui.js" = 'tgui/assets/tgui.js', - // tgui-next - "tgui-main.html" = 'tgui-next/packages/tgui/public/tgui-main.html', - "tgui-fallback.html" = 'tgui-next/packages/tgui/public/tgui-fallback.html', - "tgui.bundle.js" = 'tgui-next/packages/tgui/public/tgui.bundle.js', - "tgui.bundle.css" = 'tgui-next/packages/tgui/public/tgui.bundle.css', - "shim-html5shiv.js" = 'tgui-next/packages/tgui/public/shim-html5shiv.js', - "shim-ie8.js" = 'tgui-next/packages/tgui/public/shim-ie8.js', - "shim-dom4.js" = 'tgui-next/packages/tgui/public/shim-dom4.js', - "shim-css-om.js" = 'tgui-next/packages/tgui/public/shim-css-om.js', - )*/ - -/datum/asset/group/tgui - children = list( - /datum/asset/simple/tgui, - /datum/asset/simple/fontawesome - ) - -/datum/asset/simple/headers -/* assets = list( - "alarm_green.gif" = 'icons/program_icons/alarm_green.gif', - "alarm_red.gif" = 'icons/program_icons/alarm_red.gif', - "batt_5.gif" = 'icons/program_icons/batt_5.gif', - "batt_20.gif" = 'icons/program_icons/batt_20.gif', - "batt_40.gif" = 'icons/program_icons/batt_40.gif', - "batt_60.gif" = 'icons/program_icons/batt_60.gif', - "batt_80.gif" = 'icons/program_icons/batt_80.gif', - "batt_100.gif" = 'icons/program_icons/batt_100.gif', - "charging.gif" = 'icons/program_icons/charging.gif', - "downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif', - "downloader_running.gif" = 'icons/program_icons/downloader_running.gif', - "ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif', - "ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif', - "power_norm.gif" = 'icons/program_icons/power_norm.gif', - "power_warn.gif" = 'icons/program_icons/power_warn.gif', - "sig_high.gif" = 'icons/program_icons/sig_high.gif', - "sig_low.gif" = 'icons/program_icons/sig_low.gif', - "sig_lan.gif" = 'icons/program_icons/sig_lan.gif', - "sig_none.gif" = 'icons/program_icons/sig_none.gif', - "smmon_0.gif" = 'icons/program_icons/smmon_0.gif', - "smmon_1.gif" = 'icons/program_icons/smmon_1.gif', - "smmon_2.gif" = 'icons/program_icons/smmon_2.gif', - "smmon_3.gif" = 'icons/program_icons/smmon_3.gif', - "smmon_4.gif" = 'icons/program_icons/smmon_4.gif', - "smmon_5.gif" = 'icons/program_icons/smmon_5.gif', - "smmon_6.gif" = 'icons/program_icons/smmon_6.gif' - )*/ - -/datum/asset/spritesheet/simple/pda - name = "pda" -/* assets = list( - "atmos" = 'icons/pda_icons/pda_atmos.png', - "back" = 'icons/pda_icons/pda_back.png', - "bell" = 'icons/pda_icons/pda_bell.png', - "blank" = 'icons/pda_icons/pda_blank.png', - "boom" = 'icons/pda_icons/pda_boom.png', - "bucket" = 'icons/pda_icons/pda_bucket.png', - "medbot" = 'icons/pda_icons/pda_medbot.png', - "floorbot" = 'icons/pda_icons/pda_floorbot.png', - "cleanbot" = 'icons/pda_icons/pda_cleanbot.png', - "crate" = 'icons/pda_icons/pda_crate.png', - "cuffs" = 'icons/pda_icons/pda_cuffs.png', - "eject" = 'icons/pda_icons/pda_eject.png', - "flashlight" = 'icons/pda_icons/pda_flashlight.png', - "honk" = 'icons/pda_icons/pda_honk.png', - "mail" = 'icons/pda_icons/pda_mail.png', - "medical" = 'icons/pda_icons/pda_medical.png', - "menu" = 'icons/pda_icons/pda_menu.png', - "mule" = 'icons/pda_icons/pda_mule.png', - "notes" = 'icons/pda_icons/pda_notes.png', - "power" = 'icons/pda_icons/pda_power.png', - "rdoor" = 'icons/pda_icons/pda_rdoor.png', - "reagent" = 'icons/pda_icons/pda_reagent.png', - "refresh" = 'icons/pda_icons/pda_refresh.png', - "scanner" = 'icons/pda_icons/pda_scanner.png', - "signaler" = 'icons/pda_icons/pda_signaler.png', - "status" = 'icons/pda_icons/pda_status.png', - "dronephone" = 'icons/pda_icons/pda_dronephone.png', - "emoji" = 'icons/pda_icons/pda_emoji.png' - )*/ - -/datum/asset/spritesheet/simple/paper - name = "paper" -/* assets = list( - "stamp-clown" = 'icons/stamp_icons/large_stamp-clown.png', - "stamp-deny" = 'icons/stamp_icons/large_stamp-deny.png', - "stamp-ok" = 'icons/stamp_icons/large_stamp-ok.png', - "stamp-hop" = 'icons/stamp_icons/large_stamp-hop.png', - "stamp-cmo" = 'icons/stamp_icons/large_stamp-cmo.png', - "stamp-ce" = 'icons/stamp_icons/large_stamp-ce.png', - "stamp-hos" = 'icons/stamp_icons/large_stamp-hos.png', - "stamp-rd" = 'icons/stamp_icons/large_stamp-rd.png', - "stamp-cap" = 'icons/stamp_icons/large_stamp-cap.png', - "stamp-qm" = 'icons/stamp_icons/large_stamp-qm.png', - "stamp-law" = 'icons/stamp_icons/large_stamp-law.png' - )*/ - - -/datum/asset/simple/IRV -/* assets = list( - "jquery-ui.custom-core-widgit-mouse-sortable-min.js" = 'html/IRV/jquery-ui.custom-core-widgit-mouse-sortable-min.js', - )*/ - -/datum/asset/group/IRV - children = list( - /datum/asset/simple/jquery, - /datum/asset/simple/IRV - ) - -/datum/asset/simple/changelog -/* assets = list( - "88x31.png" = 'html/88x31.png', - "bug-minus.png" = 'html/bug-minus.png', - "cross-circle.png" = 'html/cross-circle.png', - "hard-hat-exclamation.png" = 'html/hard-hat-exclamation.png', - "image-minus.png" = 'html/image-minus.png', - "image-plus.png" = 'html/image-plus.png', - "music-minus.png" = 'html/music-minus.png', - "music-plus.png" = 'html/music-plus.png', - "tick-circle.png" = 'html/tick-circle.png', - "wrench-screwdriver.png" = 'html/wrench-screwdriver.png', - "spell-check.png" = 'html/spell-check.png', - "burn-exclamation.png" = 'html/burn-exclamation.png', - "chevron.png" = 'html/chevron.png', - "chevron-expand.png" = 'html/chevron-expand.png', - "scales.png" = 'html/scales.png', - "coding.png" = 'html/coding.png', - "ban.png" = 'html/ban.png', - "chrome-wrench.png" = 'html/chrome-wrench.png', - "changelog.css" = 'html/changelog.css' - )*/ - -/datum/asset/group/goonchat - children = list( - /datum/asset/simple/jquery, - /datum/asset/simple/goonchat, - /datum/asset/spritesheet/goonchat, - /datum/asset/simple/fontawesome - ) - - -/datum/asset/simple/jquery - verify = FALSE -/* assets = list( - "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js', - )*/ - -/datum/asset/simple/goonchat - verify = FALSE -/* assets = list( - "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js', - "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js', - "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', - "browserOutput_white.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', - )*/ - -/datum/asset/simple/fontawesome - verify = FALSE -/* assets = list( - "fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot', - "fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff', - "fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot', - "fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff', - "font-awesome.css" = 'html/font-awesome/css/all.min.css', - "v4shim.css" = 'html/font-awesome/css/v4-shims.min.css' - )*/ - -/datum/asset/simple/blackedstone_class_menu_slop_layout - verify = FALSE - assets = list( - "try4.png" = 'icons/roguetown/misc/try4.png', - "try4_border.png" = 'icons/roguetown/misc/try4_border.png', - "slop_menustyle2.css" = 'html/browser/slop_menustyle2.css', - "haha_skull.gif" = 'icons/roguetown/misc/haha_skull.gif' - ) - -/datum/asset/simple/blackedstone_triumph_buy_menu_slop_layout - verify = FALSE - assets = list( - "try5.png" = 'icons/roguetown/misc/try5.png', - "try5_border.png" = 'icons/roguetown/misc/try5_border.png', - "slop_menustyle3.css" = 'html/browser/slop_menustyle3.css' - ) - -/datum/asset/simple/roguefonts - verify = TRUE - assets = list( - "pterra.ttf" = 'interface/fonts/pterra.ttf', - "chiseld.ttf" = 'interface/fonts/chiseld.ttf', - "blackmoor.ttf" = 'interface/fonts/blackmoor.ttf', - "handwrite.ttf" = 'interface/fonts/handwrite.ttf', - "book1.ttf" = 'interface/fonts/book1.ttf', - "book2.ttf" = 'interface/fonts/book1.ttf', - "book3.ttf" = 'interface/fonts/book1.ttf', - "book4.ttf" = 'interface/fonts/book1.ttf', - "dwarf.ttf" = 'interface/fonts/languages/dwarf.ttf', - "elf.ttf" = 'interface/fonts/languages/elf.ttf', - "hell.ttf" = 'interface/fonts/languages/hell.ttf', - "orc.ttf" = 'interface/fonts/languages/orc.ttf', - "sand.ttf" = 'interface/fonts/languages/sand.ttf', - "undead.ttf" = 'interface/fonts/languages/undead.ttf', - "draconic.ttf" = 'interface/fonts/languages/draconic.ttf', - "fae.ttf" = 'interface/fonts/languages/fae.ttf', - "lupian.ttf" = 'interface/fonts/languages/lupian.ttf', - "felid.ttf" = 'interface/fonts/languages/felid.ttf' - ) - -/datum/asset/spritesheet/goonchat - name = "chat" - -/datum/asset/spritesheet/goonchat/register() -/* InsertAll("emoji", 'icons/emoji.dmi') - - // pre-loading all lanugage icons also helps to avoid meta - InsertAll("language", 'icons/misc/language.dmi') - // catch languages which are pulling icons from another file - for(var/path in typesof(/datum/language)) - var/datum/language/L = path - var/icon = initial(L.icon) - if (icon != 'icons/misc/language.dmi') - var/icon_state = initial(L.icon_state) - Insert("language-[icon_state]", icon, icon_state=icon_state) -*/ - ..() - -/datum/asset/simple/permissions -/* assets = list( - "padlock.png" = 'html/padlock.png' - )*/ - -/datum/asset/simple/notes -/* assets = list( - "high_button.png" = 'html/high_button.png', - "medium_button.png" = 'html/medium_button.png', - "minor_button.png" = 'html/minor_button.png', - "none_button.png" = 'html/none_button.png', - )*/ - -/datum/asset/spritesheet/simple/achievements - name ="achievements" -/* assets = list( - "default" = 'icons/UI_Icons/Achievements/default.png' - )*/ - -/datum/asset/spritesheet/simple/pills - name ="pills" -/* assets = list( - "pill1" = 'icons/UI_Icons/Pills/pill1.png', - "pill2" = 'icons/UI_Icons/Pills/pill2.png', - "pill3" = 'icons/UI_Icons/Pills/pill3.png', - "pill4" = 'icons/UI_Icons/Pills/pill4.png', - "pill5" = 'icons/UI_Icons/Pills/pill5.png', - "pill6" = 'icons/UI_Icons/Pills/pill6.png', - "pill7" = 'icons/UI_Icons/Pills/pill7.png', - "pill8" = 'icons/UI_Icons/Pills/pill8.png', - "pill9" = 'icons/UI_Icons/Pills/pill9.png', - "pill10" = 'icons/UI_Icons/Pills/pill10.png', - "pill11" = 'icons/UI_Icons/Pills/pill11.png', - "pill12" = 'icons/UI_Icons/Pills/pill12.png', - "pill13" = 'icons/UI_Icons/Pills/pill13.png', - "pill14" = 'icons/UI_Icons/Pills/pill14.png', - "pill15" = 'icons/UI_Icons/Pills/pill15.png', - "pill16" = 'icons/UI_Icons/Pills/pill16.png', - "pill17" = 'icons/UI_Icons/Pills/pill17.png', - "pill18" = 'icons/UI_Icons/Pills/pill18.png', - "pill19" = 'icons/UI_Icons/Pills/pill19.png', - "pill20" = 'icons/UI_Icons/Pills/pill20.png', - "pill21" = 'icons/UI_Icons/Pills/pill21.png', - "pill22" = 'icons/UI_Icons/Pills/pill22.png', - )*/ - - -/datum/asset/spritesheet/simple/roulette - name = "roulette" -/* assets = list( - "black" = 'icons/UI_Icons/Roulette/black.png', - "red" = 'icons/UI_Icons/Roulette/red.png', - "odd" = 'icons/UI_Icons/Roulette/odd.png', - "even" = 'icons/UI_Icons/Roulette/even.png', - "low" = 'icons/UI_Icons/Roulette/1-18.png', - "high" = 'icons/UI_Icons/Roulette/19-36.png', - "nano" = 'icons/UI_Icons/Roulette/nano.png', - "zero" = 'icons/UI_Icons/Roulette/0.png' - )*/ - - - - -//this exists purely to avoid meta by pre-loading all language icons. -/datum/asset/language/register() - for(var/path in typesof(/datum/language)) - set waitfor = FALSE - var/datum/language/L = new path () - L.get_icon() - -/datum/asset/spritesheet/pipes - name = "pipes" - -/datum/asset/spritesheet/pipes/register() -/* for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi', 'icons/obj/atmospherics/pipes/transit_tube.dmi', 'icons/obj/plumbing/fluid_ducts.dmi')) - InsertAll("", each, GLOB.alldirs)*/ - ..() - -// Representative icons for each research design -/datum/asset/spritesheet/research_designs - name = "design" - -/datum/asset/spritesheet/research_designs/register() -/* for (var/path in subtypesof(/datum/design)) - var/datum/design/D = path - - var/icon_file - var/icon_state - var/icon/I - - if(initial(D.research_icon) && initial(D.research_icon_state)) //If the design has an icon replacement skip the rest - icon_file = initial(D.research_icon) - icon_state = initial(D.research_icon_state) -// if(!(icon_state in icon_states(icon_file))) -// warning("design [D] with icon '[icon_file]' missing state '[icon_state]'") -// continue - I = icon(icon_file, icon_state, SOUTH) - - else - // construct the icon and slap it into the resource cache - var/atom/item = initial(D.build_path) - if (!ispath(item, /atom)) - // biogenerator outputs to beakers by default - if (initial(D.build_type) & BIOGENERATOR) - item = /obj/item/reagent_containers/glass/beaker/large - else - continue // shouldn't happen, but just in case - - // circuit boards become their resulting machines or computers - if (ispath(item, /obj/item/circuitboard)) - var/obj/item/circuitboard/C = item - var/machine = initial(C.build_path) - if (machine) - item = machine - - icon_file = initial(item.icon) - icon_state = initial(item.icon_state) - -// if(!(icon_state in icon_states(icon_file))) -// warning("design [D] with icon '[icon_file]' missing state '[icon_state]'") -// continue - I = icon(icon_file, icon_state, SOUTH) - - // computers (and snowflakes) get their screen and keyboard sprites - if (ispath(item, /obj/machinery/computer) || ispath(item, /obj/machinery/power/solar_control)) - var/obj/machinery/computer/C = item - var/screen = initial(C.icon_screen) - var/keyboard = initial(C.icon_keyboard) - var/all_states = icon_states(icon_file) - if (screen && (screen in all_states)) - I.Blend(icon(icon_file, screen, SOUTH), ICON_OVERLAY) - if (keyboard && (keyboard in all_states)) - I.Blend(icon(icon_file, keyboard, SOUTH), ICON_OVERLAY) - - Insert(initial(D.id), I)*/ - return ..() - -/datum/asset/spritesheet/vending - name = "vending" - -/datum/asset/spritesheet/vending/register() -/* for (var/k in GLOB.vending_products) - var/atom/item = k - if (!ispath(item, /atom)) - continue - - var/icon_file = initial(item.icon) - var/icon_state = initial(item.icon_state) - var/icon/I - - var/icon_states_list = icon_states(icon_file) - if(icon_state in icon_states_list) - I = icon(icon_file, icon_state, SOUTH) - var/c = initial(item.color) - if (!isnull(c) && c != "#FFFFFF") - I.Blend(c, ICON_MULTIPLY) - else - var/icon_states_string - for (var/an_icon_state in icon_states_list) - if (!icon_states_string) - icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])" - else - icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])" -// stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]") - I = icon('icons/turf/floors.dmi', "", SOUTH) - - var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-") - - Insert(imgid, I)*/ - return ..() - -/datum/asset/simple/genetics -/* assets = list( - "dna_discovered.gif" = 'html/dna_discovered.gif', - "dna_undiscovered.gif" = 'html/dna_undiscovered.gif', - "dna_extra.gif" = 'html/dna_extra.gif' - )*/ - assets = null diff --git a/code/modules/client/asset_configs.dm b/code/modules/client/asset_configs.dm new file mode 100644 index 00000000000..c839ccc078d --- /dev/null +++ b/code/modules/client/asset_configs.dm @@ -0,0 +1,30 @@ +/datum/config_entry/keyed_list/external_rsc_urls + key_mode = KEY_MODE_TEXT + value_mode = VALUE_MODE_FLAG + +/datum/config_entry/flag/asset_simple_preload + +/datum/config_entry/string/asset_transport +/datum/config_entry/string/asset_transport/ValidateAndSet(str_val) + return (lowertext(str_val) in list("simple", "webroot")) && ..(lowertext(str_val)) + +/datum/config_entry/string/asset_cdn_webroot + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/string/asset_cdn_webroot/ValidateAndSet(str_var) + if (!str_var || trim(str_var) == "") + return FALSE + if (str_var && str_var[length(str_var)] != "/") + str_var += "/" + return ..(str_var) + +/datum/config_entry/string/asset_cdn_url + protection = CONFIG_ENTRY_LOCKED + default = null + +/datum/config_entry/string/asset_cdn_url/ValidateAndSet(str_var) + if (!str_var || trim(str_var) == "") + return FALSE + if (str_var && str_var[length(str_var)] != "/") + str_var += "/" + return ..(str_var) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 549069fea3b..af1805c9939 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -223,13 +223,10 @@ GLOBAL_LIST_EMPTY(respawncounts) /////////// //CONNECT// /////////// -#if (PRELOAD_RSC == 0) -GLOBAL_LIST_EMPTY(external_rsc_urls) -#endif /client/New(TopicData) var/tdata = TopicData //save this for later use -// chatOutput = new /datum/chatOutput(src) + chatOutput = new /datum/chatOutput(src) TopicData = null //Prevent calls to client.Topic from connect if(connection != "seeker" && connection != "web")//Invalid connection type. @@ -282,6 +279,10 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) prefs.last_id = computer_id //these are gonna be used for banning fps = prefs.clientfps + if(prefs.prefer_old_chat == FALSE) + spawn() // Goonchat does some non-instant checks in start() + chatOutput.start() + if(fexists(roundend_report_file())) verbs += /client/proc/show_previous_roundend_report @@ -963,27 +964,20 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) /client/proc/send_resources() #if (PRELOAD_RSC == 0) var/static/next_external_rsc = 0 - if(GLOB.external_rsc_urls && GLOB.external_rsc_urls.len) - next_external_rsc = WRAP(next_external_rsc+1, 1, GLOB.external_rsc_urls.len+1) - preload_rsc = GLOB.external_rsc_urls[next_external_rsc] + var/list/external_rsc_urls = CONFIG_GET(keyed_list/external_rsc_urls) + if(length(external_rsc_urls)) + next_external_rsc = WRAP(next_external_rsc+1, 1, external_rsc_urls.len+1) + preload_rsc = external_rsc_urls[next_external_rsc] #endif - //get the common files - getFiles( - 'html/search.js', - 'html/panels.css', - 'html/browser/common.css', - 'html/browser/scannernew.css', - 'html/browser/playeroptions.css', - ) - spawn (10) //removing this spawn causes all clients to not get verbs. + + spawn (10) //removing this spawn causes all clients to not get verbs. (this can't be addtimer because these assets may be needed before the mc inits) + + //load info on what assets the client has + src << browse('code/modules/asset_cache/validate_assets.html', "window=asset_cache_browser") + //Precache the client with all other assets slowly, so as to not block other browse() calls - getFilesSlow(src, SSassets.preload, register_asset = FALSE) -// #if (PRELOAD_RSC == 0) -// for (var/name in GLOB.vox_sounds) -// var/file = GLOB.vox_sounds[name] -// Export("##action=load_rsc", file) -// stoplag() -// #endif + if (CONFIG_GET(flag/asset_simple_preload)) + addtimer(CALLBACK(SSassets.transport, TYPE_PROC_REF(/datum/asset_transport, send_assets_slow), src, SSassets.transport.preload), 5 SECONDS) //Hook, override it to run code when dir changes //Like for /atoms, but clients are their own snowflake FUCK diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index b6393a0ebaa..c373f7f8ff0 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -161,7 +161,7 @@ GLOBAL_LIST_INIT(name_adjustments, list()) var/char_accent = "No accent" /// Tracker to whether the person has ever spawned into the round, for purposes of applying the respawn ban var/has_spawned = FALSE - + var/prefer_old_chat = FALSE /datum/preferences/New(client/C) parent = C @@ -838,13 +838,13 @@ GLOBAL_LIST_INIT(name_adjustments, list()) dat += "" if(usr?.client?.prefs?.be_russian) - dat += "[next_special_trait ? "ОСОБЕННЫЙ" : "Быть Особенным"]
" + dat += "
Доп. Предмет: [loadout ? loadout.name : "None"]
" else - dat += "[next_special_trait ? "SPECIAL" : "Be Special"]
" + dat += "
Loadout Item: [loadout ? loadout.name : "None"]
" if(usr?.client?.prefs?.be_russian) - dat += "Предыстория: [background.ru_name]
" + dat += "[next_special_trait ? "ОСОБЕННЫЙ" : "Быть Особенным"]
" else - dat += "Background: [background.name]
" + dat += "[next_special_trait ? "SPECIAL" : "Be Special"]
" if(istype(N)) if(SSticker.current_state <= GAME_STATE_PREGAME) switch(N.ready) @@ -1658,29 +1658,6 @@ Slots: [job.spawn_positions] return voice_color = sanitize_hexcolor(new_voice) - if ("background") - var/list/backgrounds_available = list() - for (var/path as anything in GLOB.backgrounds) - var/datum/background/background = GLOB.backgrounds[path] - if(usr?.client?.prefs?.be_russian) - if (!background.ru_name) - continue - backgrounds_available[background.ru_name] = background - else - if (!background.name) - continue - backgrounds_available[background.name] = background - var/background_input = input(user, "Choose your character's background", "Background") as null|anything in backgrounds_available - if (background_input) - var/datum/background/background_chosen = backgrounds_available[background_input] - background = background_chosen - if(usr?.client?.prefs?.be_russian) - to_chat(user, "[background.ru_name]") - to_chat(user, "[background.ru_description_string()]") - else - to_chat(user, "[background.name]") - to_chat(user, "[background.description_string()]") - if("barksound") var/list/woof_woof = list() for(var/path in GLOB.bark_list) @@ -1788,6 +1765,24 @@ Slots: [job.spawn_positions] to_chat(user, "Successfully updated nudeshot picture") log_game("[user] has set their Nudeshot image to '[nudeshot_link]'.") + if("loadout_item") + var/list/loadouts_available = list("None") + for (var/path as anything in GLOB.loadout_items) + var/datum/loadout_item/loadout = GLOB.loadout_items[path] + if (!loadout.name) + continue + loadouts_available[loadout.name] = loadout + var/loadout_input = input(user, "Choose your character's loadout item.", "Loadout") as null|anything in loadouts_available + if(loadout_input) + if(loadout_input == "None") + loadout = null + to_chat(user, "Who needs stuff anyway?") + else + loadout = loadouts_available[loadout_input] + to_chat(user, "[loadout.name]") + if(loadout.desc) + to_chat(user, "[loadout.desc]") + if("species") var/list/crap = list() @@ -2431,7 +2426,6 @@ Slots: [job.spawn_positions] character.headshot_link = headshot_link character.nudeshot_link = nudeshot_link - character.background = background character.set_bark(bark_id) character.vocal_speed = bark_speed character.vocal_pitch = bark_pitch diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 1e5e9a759af..2e3b5ad6330 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -165,6 +165,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["crt"] >> crt S["mastervol"] >> mastervol S["lastclass"] >> lastclass + S["prefer_old_chat"] >> prefer_old_chat S["default_slot"] >> default_slot @@ -293,6 +294,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["pda_color"], pda_color) WRITE_FILE(S["key_bindings"], key_bindings) WRITE_FILE(S["defiant"], defiant) + WRITE_FILE(S["prefer_old_chat"], prefer_old_chat) return TRUE diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index 3ce6291e579..4a936b31f50 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -336,10 +336,25 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") // testing("reges cant find") // return "0" +/client/verb/html_chat() + set name = "{Old Chat}" + set category = "Options" + set hidden = FALSE + + prefs.prefer_old_chat = TRUE + prefs.save_preferences() + to_chat(src, "Going back to old chat.") + + winset(src, "output", "is-visible=true;is-disabled=false") + winset(src, "browseroutput", "is-visible=false") + /client/verb/fix_chat() set name = "{FIX CHAT}" set category = "Options" set hidden = FALSE + + prefs.prefer_old_chat = FALSE + prefs.save_preferences() if(!check_rights(0)) return if (!chatOutput || !istype(chatOutput)) diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm index ddfd0d3f4f9..82e3531cb2f 100644 --- a/code/modules/goonchat/browserOutput.dm +++ b/code/modules/goonchat/browserOutput.dm @@ -1,3 +1,5 @@ +#define MAX_COOKIE_LENGTH 5 + /********************************* For the main html chat area *********************************/ @@ -9,11 +11,14 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico /datum/chatOutput var/client/owner //client ref var/loaded = FALSE // Has the client loaded the browser output area? - var/list/messageQueue //If they haven't loaded chat, this is where messages will go until they do + var/list/messageQueue = list()//If they haven't loaded chat, this is where messages will go until they do var/cookieSent = FALSE // Has the client sent a cookie for analysis var/broken = FALSE var/list/connectionHistory //Contains the connection history passed from chat cookie var/adminMusicVolume = 50 //This is for the Play Global Sound verb + var/total_checks = 0 + var/load_attempts = 0 + /datum/chatOutput/New(client/C) owner = C @@ -33,6 +38,9 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico alert(owner.mob, "Updated chat window does not exist. If you are using a custom skin file please allow the game to update.") return + if(!owner) // In case the client vanishes before winexists returns + return 0 + if(winget(owner, "browseroutput", "is-visible") == "true") //Already setup doneLoading() @@ -45,16 +53,31 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico set waitfor = FALSE if(!owner) return - + if(loaded) + return var/datum/asset/stuff = get_asset_datum(/datum/asset/group/goonchat) stuff.send(owner) owner << browse(file('code/modules/goonchat/browserassets/html/browserOutput.html'), "window=browseroutput") + if (load_attempts < 5) //To a max of 5 load attempts + spawn(20 SECONDS) + if (owner && !loaded) + load_attempts++ + load() + else + return + /datum/chatOutput/Topic(href, list/href_list) if(usr.client != owner) return TRUE + if(href_list["admin_command"]) + if(!owner.holder) + return + owner.holder.admin_command(href_list["admin_command"], href_list["target"]) + return + // Build arguments. // Arguments are in the form "param[paramname]=thing" var/list/params = list() @@ -92,7 +115,7 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico //Called on chat output done-loading by JS. /datum/chatOutput/proc/doneLoading() - if(loaded) + if(loaded || !owner) return testing("Chat loaded for [owner.ckey]") @@ -109,7 +132,6 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico syncRegex() - //do not convert to to_chat() SEND_TEXT(owner, "Failed to load fancy chat, reverting to old chat. Certain features won't work.") /datum/chatOutput/proc/showChat() @@ -176,10 +198,19 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("tmp/iconCache.sav")) //Cache of ico return if(cookie != "none") + var/regex/simple_crash_regex = new /regex("(\\\[ *){5}") + if(simple_crash_regex.Find(cookie)) + message_admins("[key_name(src.owner)] tried to crash the server using malformed JSON") + log_admin("[key_name(owner)] tried to crash the server using malformed JSON") + return var/list/connData = json_decode(cookie) if (connData && islist(connData) && connData.len > 0 && connData["connData"]) connectionHistory = connData["connData"] //lol fuck var/list/found = new() + if(connectionHistory.len > MAX_COOKIE_LENGTH) + message_admins("[key_name(src.owner)] was kicked for an invalid ban cookie)") + qdel(owner) + return for(var/i in connectionHistory.len to 1 step -1) var/list/row = src.connectionHistory[i] if (!row || row.len < 3 || (!row["ckey"] || !row["compid"] || !row["ip"])) //Passed malformed history object diff --git a/code/modules/goonchat/browserassets/css/browserOutput.css b/code/modules/goonchat/browserassets/css/browserOutput.css index 94461d1c237..b1e213e9ace 100644 --- a/code/modules/goonchat/browserassets/css/browserOutput.css +++ b/code/modules/goonchat/browserassets/css/browserOutput.css @@ -11,7 +11,7 @@ html, body { } body { background: #000000; - font-family: Pterra, TrueType; + font-family: Pterra; font-size: 13px; color: #c9c1ba; line-height: 1.2; @@ -24,6 +24,59 @@ body { text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000; } + +/* Fonts */ +@font-face { + font-family: "Pterra"; + src: url('pterra.ttf') format('truetype'); +} +@font-face { + font-family: "Honoka Mincho"; + src: url('HonokaMincho.ttf') format('truetype'); +} + +@font-face { + font-family: "Dauphin"; + src: url('elf.ttf') format('truetype'); +} + +@font-face { + font-family: "MasonAlternate"; + src: url('dwarf.ttf') format('truetype'); +} + +@font-face { + font-family: "Arabolical"; + src: url('sand.ttf') format('truetype'); +} + +@font-face { + font-family: "Xaphan"; + src: url('hell.ttf') format('truetype'); +} + +@font-face { + font-family: "FriskyVampire"; + src: url('undead.ttf') format('truetype'); +} + +@font-face { + font-family: "Thief by The Riddler"; + src: url('orc.ttf') format('truetype'); +} + +@font-face { + font-family: "Kingthings Petrock"; + src: url('draconic.ttf') format('truetype'); +} + +@font-face { + font-family: "Emperialisme"; + src: url('lupian.ttf') format('truetype'); +} + +/* */ + em { font-style: normal; font-weight: bold; @@ -51,7 +104,7 @@ img.icon { min-width: 0.5em; font-size: 0.7em; padding: 0.2em 0.3em; - line-height: 1; + line-height: 1.2; color: white; text-align: center; white-space: nowrap; @@ -141,7 +194,7 @@ a.popt {text-decoration: none;} float: right; text-align: center; } -#userBar .sub {clear: both; display: none; width: 180px;} +#userBar .sub {clear: both; display: none; width: 200px;} #userBar .sub.scroll {overflow-y: scroll;} #userBar .sub.subCell {padding: 3px 0 3px 8px; line-height: 30px; font-size: 0.9em; clear: both;} #userBar .sub span { @@ -245,35 +298,36 @@ a.popt {text-decoration: none;} * ******************************************/ -/* MOTD */ -.motd {color: #c9c1ba; font-family: Pterra, TrueType;} -.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 {color: #c9c1ba; text-decoration: underline;} -.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #c9c1ba;} +body {word-wrap: break-word; overflow-x: hidden; overflow-y: scroll; color: #c9c1ba; font-size: 16px; font-family: "Pterra";} -/* ADD HERE FOR BOLD */ -.bold, .name, .prefix, .ooc, .looc, .adminooc, .admin, .medal, .yell {font-weight: bold;} +h1, h2, h3, h4, h5, h6 {color: #c9c1ba; font-family: Pterra;} -/* ADD HERE FOR ITALIC */ -.italic, .italics, .emote {font-style: italic;} +em {font-style: normal; font-weight: bold; font-family: Pterra;} -/* OUTPUT COLORS */ -.highlight {background: yellow;} +a:link {color: #ae83cb; font-weight: bold;} -h1, h2, h3, h4, h5, h6 {color: #c9c1ba;font-family: Pterra, TrueType;} +.motd {color: #638500; font-family: Pterra;} +.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 + {color: #638500; text-decoration: underline;} +.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover + {color: #638500;} h1.alert, h2.alert {color: #c9c1ba;font-family: Pterra, TrueType;} +.italics {font-style: italic;} + +.bold {font-weight: bold;} -em {font-style: normal; font-weight: bold;} +.prefix {font-weight: bold;} -.ooc {color: #cca300; font-weight: bold;} -.adminobserverooc {color: #0099cc; font-weight: bold;} -.adminooc {color: #3d5bc3; font-weight: bold;} +.ooc {color: #c5c5c5; font-weight: bold; font-family: Pterra;} +.adminobserverooc {color: #cca300; font-weight: bold; font-family: Pterra;} +.adminooc {color: #4972bc; font-weight: bold;} -.adminsay {color: #ff4500; font-weight: bold;} -.admin {color: #5975da; font-weight: bold;} +.adminsay {color: #FF4500; font-weight: bold;} +.admin {color: #386aff; font-weight: bold;} .name { font-weight: bold;} -.say {} +.say {font-family: Pterra;} .deadsay {color: #e2c1ff;} .binarysay {color: #20c20e; background-color: #000000; display: block;} .binarysay a {color: #00ff00;} @@ -292,112 +346,122 @@ em {font-style: normal; font-weight: bold;} .redteamradio {color: #ff4444;} .blueteamradio {color: #3434fd;} -.yell { font-weight: bold;} + +.yell {font-weight: bold;} .alert {color: #d82020;} .emote {color: #b1bb9f; font-size: 75%;} -.userdanger {color: #c71d1d; font-weight: bold; font-size: 115%;} +.crit {color: #c71d76;} +.userdanger {color: #c71d76; font-weight: bold; font-size: 120%;} .danger {color: #b9322b; font-weight: bold;} -.warning {color: #b9322b; font-size: 85%;} -.alertwarning {color: #FF0000; font-weight: bold} -.boldwarning {color: #b9322b; font-weight: bold} +.warning {color: #bb4e28; font-size: 75%;} +.boldwarning {color: #bb4e28; font-weight: bold} .announce {color: #c51e1e; font-weight: bold;} .boldannounce {color: #c51e1e; font-weight: bold;} .greenannounce {color: #059223; font-weight: bold;} -.rose {color: #ff5050;} -.info {color: #a9a5b6; font-size: 85%;} +.rose {color: #e7bed8;} +.love {color: #e7bed8; font-size: 75%;} +.info {color: #a9a5b6; font-size: 75%; line-height:1} +.biginfo {color: #a9a5b6;} .notice {color: #f1d669;} .boldnotice {color: #f1d669; font-weight: bold;} +.smallnotice {color: #f1d669; font-size: 75%;} .hear {color: #6685f5; font-style: italic;} .adminnotice {color: #6685f5;} .adminhelp {color: #ff0000; font-weight: bold;} .unconscious {color: #c9c1ba; font-weight: bold;} .suicide {color: #ff5050; font-style: italic;} .green {color: #80b077;} -.red {color: #b84d47} -.blue {color: #6a8cb7} +.smallgreen {color: #80b077; font-size: 75%;} +.boldgreen {color: #80b077; font-weight: bold;} +.red {color: #b84d47;} +.smallred {color: #b84d47; font-size: 75%;} +.boldred {color: #b84d47; font-weight: bold;} +.blue {color: #6a8cb7;} .purple {color: #967aaf;} .nicegreen {color: #9bccd0;} - -.cult {color: #aa1c1c;} -.cultitalic {color: #aa1c1c; font-style: italic;} -.cultbold {color: #aa1c1c; font-style: italic; font-weight: bold;} -.cultboldtalic {color: #aa1c1c; font-weight: bold; font-size: 185%;} - -.cultlarge {color: #aa1c1c; font-weight: bold; font-size: 185%;} -.narsie {color: #aa1c1c; font-weight: bold; font-size: 925%;} -.narsiesmall {color: #aa1c1c; font-weight: bold; font-size: 370%;} -.colossus {color: #7F282A; font-size: 310%;} -.hierophant {color: #b441ee; font-weight: bold; font-style: italic;} -.hierophant_warning {color: #c56bf1; font-style: italic;} -.holoparasite {color: #88809c;} +.cult {color: #960000;} +.cultlarge {color: #960000; font-weight: bold; font-size: 3;} +.cultsmall {color: #960000; font-size: 75%;} +.narsie {color: #960000; font-weight: bold; font-size: 12;} +.narsiesmall {color: #960000; font-weight: bold; font-size: 6;} +.colossus {color: #7F282A; font-size: 5;} +.hierophant {color: #660099; font-weight: bold; font-style: italic;} +.hierophant_warning {color: #660099; font-style: italic;} +.purple {color: #5e2d79;} +.holoparasite {color: #35333a;} +.beautifulmasc {color: #083eab;} +.beautifulfem {color: #d70a74;} +.beautifulnb {color: #9656c9;} .revennotice {color: #1d2953;} .revenboldnotice {color: #1d2953; font-weight: bold;} -.revenbignotice {color: #1d2953; font-weight: bold; font-size: 185%;} +.revenbignotice {color: #1d2953; font-weight: bold; font-size: 3;} .revenminor {color: #823abb} .revenwarning {color: #760fbb; font-style: italic;} -.revendanger {color: #760fbb; font-weight: bold; font-size: 185%;} +.revendanger {color: #760fbb; font-weight: bold; font-size: 3;} -.deconversion_message {color: #a947ff; font-size: 185%; font-style: italic;} +.deconversion_message {color: #5000A0; font-size: 3; font-style: italic;} -.ghostalert {color: #6600ff; font-style: italic; font-weight: bold;} +.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;} -.alien {color: #855d85;} -.noticealien {color: #059223;} -.alertalien {color: #059223; font-weight: bold;} -.changeling {color: #059223; font-style: italic;} -.alertsyndie {color: #FF0000; font-size: 185%; font-weight: bold;} +.alien {color: #543354;} +.noticealien {color: #00c000;} +.alertalien {color: #00c000; font-weight: bold;} +.changeling {color: #800080; font-style: italic;} -.spider {color: #8800ff; font-weight: bold; font-size: 185%;} +.spider {color: #4d004d;} -.interface {color: #750e75;} +.interface {color: #330033;} .sans {font-family: "Comic Sans MS", cursive, sans-serif;} .papyrus {font-family: "Papyrus", cursive, sans-serif;} .robot {font-family: "Courier New", cursive, sans-serif;} -.command_headset {font-weight: bold; font-size: 160%;} -.small {font-size: 75%;} +.human {font-family: "Honoka Mincho", "Pterra";} +.elf {font-family: "Dauphin", cursive, "Pterra";} +.dwarf {font-family: "MasonAlternate", "Pterra";} +.sandspeak {font-family: "Arabolical", "Pterra";} +.delf {font-family: "Dauphin", "Pterra";} +.hellspeak {font-family: "Xaphan", "Pterra"; font-size: 110%} +.undead {font-family: "FriskyVampire", "Pterra";} +.orc {font-family: "Thief by The Riddler", Pterra;} +.beast {font-family: "Thief by The Riddler", Pterra;} +.reptile {font-family: "Kingthings Petrock", Pterra; font-size: 120%;} +.lupian {font-family: "Emperialisme", Pterra; font-size: 120%;} + +.torture {color: #42ff20} + +.command_headset {font-weight: bold; font-size: 3;} +.small {font-size: 50%;} +.smallyell {font-size: 70%;font-family: Pterra;} .big {font-size: 120%;} .reallybig {font-size: 180%;} .extremelybig {font-size: 220%;} -.greentext {color: #3ac583; font-size: 110%;} -.redtext {color: #c73838; font-size: 110%;} -.clown {color: #ff70c1; font-size: 160%; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;} +.greentext {color: #00FF00;} +.redtext {color: #FF0000;} +.clown {color: #FF69Bf; font-size: 3; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;} .his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;} -.hypnophrase {color: #202020; font-weight: bold; animation: hypnocolor 1500ms infinite; animation-direction: alternate;} -@keyframes hypnocolor { - 0% {color: #202020;} - 25% {color: #4b02ac;} - 50% {color: #9f41f1;} - 75% {color: #541c9c;} - 100% {color: #7adbf3;} -} +.hypnophrase {color: #3bb5d3; font-weight: bold; animation: hypnocolor 1500ms infinite;} -.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;} -@keyframes phobia { - 0% {color: #f75a5a;} - 50% {color: #dd0000;} - 100% {color: #f75a5a;} -} +.phobia {color: #dd0000; font-weight: bold;} -.icon {height: 1em; width: auto;} +.icon {height: 1em; width: auto;} .memo {color: #638500; text-align: center;} -.memoedit {text-align: center; font-size: 125%;} -.abductor {color: #c204c2; font-style: italic;} -.mind_control {color: #df3da9; font-size: 100%; font-weight: bold; font-style: italic;} +.memoedit {text-align: center; font-size: 2;} +.abductor {color: #800080; font-style: italic;} +.mind_control {color: #A00D6F; font-size: 3; font-weight: bold; font-style: italic;} .slime {color: #00CED1;} .drone {color: #848482;} .monkey {color: #975032;} .swarmer {color: #2C75FF;} .resonate {color: #298F85;} -.monkeyhive {color: #a56408;} -.monkeylead {color: #af6805; font-size: 80%;} +.monkeyhive {color: #774704;} +.monkeylead {color: #774704; font-size: 2;} .dead {color: #b280df;} .bloody {color: #cc0f0f;} @@ -405,6 +469,26 @@ em {font-style: normal; font-weight: bold;} .infection {color: #77c72b;} .necrosis {color: #558d20;} .bone {color: #e3dac9;} +.love_low {color: #eac8de; font-size: 75%;} +.love_mid {color: #e9a8d1; font-size: 75%;} +.love_high {color: #f05ee1; font-size: 75%;} +.love_extreme {color: #d146f5; font-size: 75%;} +/* +@keyframes hypnocolor { + 0% {color: #202020;} + 25% {color: #4b02ac;} + 50% {color: #9f41f1;} + 75% {color: #541c9c;} + 100% {color: #7adbf3;} +} + +.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;} +@keyframes phobia { + 0% {color: #f75a5a;} + 50% {color: #dd0000;} + 100% {color: #f75a5a;} +} +*/ .connectionClosed, .fatalError {background: red; color: white; padding: 5px;} .connectionClosed.restored {background: green;} diff --git a/code/modules/goonchat/browserassets/html/adminOutput.html b/code/modules/goonchat/browserassets/html/adminOutput.html new file mode 100644 index 00000000000..7796be092dc --- /dev/null +++ b/code/modules/goonchat/browserassets/html/adminOutput.html @@ -0,0 +1,66 @@ + diff --git a/code/modules/goonchat/browserassets/html/browserOutput.html b/code/modules/goonchat/browserassets/html/browserOutput.html index 18c9b85ffdc..161aae8ed26 100644 --- a/code/modules/goonchat/browserassets/html/browserOutput.html +++ b/code/modules/goonchat/browserassets/html/browserOutput.html @@ -1,57 +1,181 @@ - + - Chat - - - - - - - + + + Chat + + + + + + -
- -
- Loading...

- If this takes longer than 30 seconds, it will automatically reload a maximum of 5 times.
- If it still doesn't work, use the bug report button at the top right of the window. -
-
-
- -
- - - +
+ +
+ Loading...

+ If this takes longer than 30 seconds, it will automatically reload a maximum of 5 times.
+ If it still doesn't work, use the bug report button at the top right of the window. +
+
+ +
+
+ + + + +
+
    +
+
+ + + + + + + + + diff --git a/code/modules/goonchat/browserassets/js/browserOutput.js b/code/modules/goonchat/browserassets/js/browserOutput.js index 241eccd66ed..1e24efbb841 100644 --- a/code/modules/goonchat/browserassets/js/browserOutput.js +++ b/code/modules/goonchat/browserassets/js/browserOutput.js @@ -8,41 +8,41 @@ //DEBUG STUFF var escaper = encodeURIComponent || escape; var decoder = decodeURIComponent || unescape; -window.onerror = function (msg, url, line, col, error) { +window.onerror = function(msg, url, line, col, error) { if (document.location.href.indexOf("proc=debug") <= 0) { var extra = !col ? '' : ' | column: ' + col; extra += !error ? '' : ' | error: ' + error; extra += !navigator.userAgent ? '' : ' | user agent: ' + navigator.userAgent; var debugLine = 'Error: ' + msg + ' | url: ' + url + ' | line: ' + line + extra; - window.location = '?_src_=chat&proc=debug¶m[error]=' + escaper(debugLine); + window.location = '?_src_=chat&proc=debug¶m[error]='+escaper(debugLine); } return true; }; //Globals window.status = 'Output'; -var $messages, $subOptions, $subAudio, $selectedSub, $contextMenu, $filterMessages, $last_message, $messagelog; +var $messages, $subOptions, $subAudio, $selectedSub, $contextMenu, $filterMessages, $last_message; var opts = { //General 'messageCount': 0, //A count...of messages... - 'messageLimit': 2053, //A limit...for the messages... + 'messageLimit': 200, //A limit...for the messages... 'scrollSnapTolerance': 10, //If within x pixels of bottom 'clickTolerance': 10, //Keep focus if outside x pixels of mousedown position on mouseup 'imageRetryDelay': 50, //how long between attempts to reload images (in ms) - 'imageRetryLimit': 50, //how many attempts should we make? + 'imageRetryLimit': 50, //how many attempts should we make? 'popups': 0, //Amount of popups opened ever 'wasd': false, //Is the user in wasd mode? 'priorChatHeight': 0, //Thing for height-resizing detection 'restarting': false, //Is the round restarting? - 'darkmode': false, //Are we using darkmode? If not WHY ARE YOU LIVING IN 2009??? + 'darkmode':false, //Are we using darkmode? If not WHY ARE YOU LIVING IN 2009??? //Options menu 'selectedSubLoop': null, //Contains the interval loop for closing the selected sub menu 'suppressSubClose': false, //Whether or not we should be hiding the selected sub menu 'highlightTerms': [], - 'highlightLimit': 5, + 'highlightLimit': 10, 'highlightColor': '#FFFF00', //The color of the highlighted message - 'pingDisabled': false, //Has the user disabled the ping counter + 'pingDisabled': true, //Has the user disabled the ping counter //Ping display 'lastPang': 0, //Timestamp of the last response from the server. @@ -80,9 +80,9 @@ function clamp(val, min, max) { } function outerHTML(el) { - var wrap = document.createElement('div'); - wrap.appendChild(el.cloneNode(true)); - return wrap.innerHTML; + var wrap = document.createElement('div'); + wrap.appendChild(el.cloneNode(true)); + return wrap.innerHTML; } //Polyfill for fucking date now because of course IE8 and below don't support it @@ -147,11 +147,11 @@ function linkify_node(node) { function linkify_fallback(text) { var rex = /((?:' + $0 + ''; + if(/^https?:\/\/.+/i.test($0)) { + return $1 ? $0: ''+$0+''; } else { - return $1 ? $0 : '' + $0 + ''; + return $1 ? $0: ''+$0+''; } }); } @@ -187,22 +187,24 @@ function replaceRegex() { function addHighlightMarkup(match) { var extra = ''; if (opts.highlightColor) { - extra += ' style="background-color: ' + opts.highlightColor + '"'; + extra += ' style="background-color: '+opts.highlightColor+'"'; } - return '' + match + ''; + return ''+match+''; } //Highlights words based on user settings function highlightTerms(el) { if (el.children.length > 0) { - for (var h = 0; h < el.children.length; h++) { + for(var h = 0; h < el.children.length; h++){ highlightTerms(el.children[h]); } } var hasTextNode = false; - for (var node = 0; node < el.childNodes.length; node++) { - if (el.childNodes[node].nodeType === 3) { + for (var node = 0; node < el.childNodes.length; node++) + { + if (el.childNodes[node].nodeType === 3) + { hasTextNode = true; break; } @@ -236,7 +238,7 @@ function highlightTerms(el) { function iconError(E) { var that = this; - setTimeout(function () { + setTimeout(function() { var attempts = $(that).data('reload_attempts'); if (typeof attempts === 'undefined' || !attempts) { attempts = 1; @@ -245,7 +247,7 @@ function iconError(E) { return; var src = that.src; that.src = null; - that.src = src + '#' + attempts; + that.src = src+'#'+attempts; $(that).data('reload_attempts', ++attempts); }, opts.imageRetryDelay); } @@ -295,7 +297,7 @@ function output(message, flag) { var tempCount = 0; for (var i = 0; i < messageClasses.length; i++) { //Every class var thisClass = messageClasses[i]; - $.each(opts.showMessagesFilters, function (key, val) { //Every filter + $.each(opts.showMessagesFilters, function(key, val) { //Every filter if (key !== 'All' && val.show === false && typeof val.match != 'undefined') { for (var i = 0; i < val.match.length; i++) { var matchClass = val.match[i]; @@ -331,7 +333,7 @@ function output(message, flag) { if ($('#newMessages').length) { $('#newMessages').remove(); } - //If not, put the new messages box in + //If not, put the new messages box in } else { if ($('#newMessages').length) { var messages = $('#newMessages .number').text(); @@ -343,7 +345,6 @@ function output(message, flag) { } } else { $messages.after('1 new message '); - $messagelog.after('1 new message '); } } } @@ -372,14 +373,14 @@ function output(message, flag) { badge = badge.detach(); badge.text(parseInt(badge.text()) + 1); } else { - badge = $('', { 'class': 'r', 'text': 2 }); + badge = $('', {'class': 'r', 'text': 2}); } lastmessages.html(message); lastmessages.find('[replaceRegex]').each(replaceRegex); lastmessages.append(badge); badge.animate({ "font-size": "0.9em" - }, 100, function () { + }, 100, function() { badge.animate({ "font-size": "0.7em" }, 100); @@ -402,18 +403,17 @@ function output(message, flag) { $last_message = trimmed_message; $messages[0].appendChild(entry); - $messagelog[0].appendChild(entry); $(entry).find("img.icon").error(iconError); var to_linkify = $(entry).find(".linkify"); if (typeof Node === 'undefined') { // Linkify fallback for old IE - for (var i = 0; i < to_linkify.length; ++i) { + for(var i = 0; i < to_linkify.length; ++i) { to_linkify[i].innerHTML = linkify_fallback(to_linkify[i].innerHTML); } } else { // Linkify for modern IE versions - for (var i = 0; i < to_linkify.length; ++i) { + for(var i = 0; i < to_linkify.length; ++i) { linkify_node(to_linkify[i]); } } @@ -430,7 +430,8 @@ function output(message, flag) { } } -function internalOutput(message, flag) { +function internalOutput(message, flag) +{ output(escaper(message), flag) } @@ -442,34 +443,34 @@ function runByond(uri) { function setCookie(cname, cvalue, exdays) { cvalue = escaper(cvalue); var d = new Date(); - d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); - var expires = 'expires=' + d.toUTCString(); + d.setTime(d.getTime() + (exdays*24*60*60*1000)); + var expires = 'expires='+d.toUTCString(); document.cookie = cname + '=' + cvalue + '; ' + expires + "; path=/"; } function getCookie(cname) { var name = cname + '='; var ca = document.cookie.split(';'); - for (var i = 0; i < ca.length; i++) { - var c = ca[i]; - while (c.charAt(0) == ' ') c = c.substring(1); + for(var i=0; i < ca.length; i++) { + var c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1); if (c.indexOf(name) === 0) { - return decoder(c.substring(name.length, c.length)); + return decoder(c.substring(name.length,c.length)); } } return ''; } -function rgbToHex(R, G, B) { return toHex(R) + toHex(G) + toHex(B); } +function rgbToHex(R,G,B) {return toHex(R)+toHex(G)+toHex(B);} function toHex(n) { - n = parseInt(n, 10); + n = parseInt(n,10); if (isNaN(n)) return "00"; - n = Math.max(0, Math.min(n, 255)); - return "0123456789ABCDEF".charAt((n - n % 16) / 16) + "0123456789ABCDEF".charAt(n % 16); + n = Math.max(0,Math.min(n,255)); + return "0123456789ABCDEF".charAt((n-n%16)/16) + "0123456789ABCDEF".charAt(n%16); } function swap() { //Swap to darkmode - if (opts.darkmode) { + if (opts.darkmode){ document.getElementById("sheetofstyles").href = "browserOutput.css"; opts.darkmode = false; runByond('?_src_=chat&proc=swaptolightmode'); @@ -483,9 +484,9 @@ function swap() { //Swap to darkmode function handleClientData(ckey, ip, compid) { //byond sends player info to here - var currentData = { 'ckey': ckey, 'ip': ip, 'compid': compid }; + var currentData = {'ckey': ckey, 'ip': ip, 'compid': compid}; if (opts.clientData && !$.isEmptyObject(opts.clientData)) { - runByond('?_src_=chat&proc=analyzeClientData¶m[cookie]=' + JSON.stringify({ 'connData': opts.clientData })); + runByond('?_src_=chat&proc=analyzeClientData¶m[cookie]='+JSON.stringify({'connData': opts.clientData})); for (var i = 0; i < opts.clientData.length; i++) { var saved = opts.clientData[i]; @@ -518,16 +519,16 @@ function ehjaxCallback(data) { runByond('?_src_=chat&proc=ping'); } else if (data == 'pong') { - if (opts.pingDisabled) { return; } + if (opts.pingDisabled) {return;} opts.pongTime = Date.now(); var pingDuration = Math.ceil((opts.pongTime - opts.pingTime) / 2); - $('#pingMs').text(pingDuration + 'ms'); + $('#pingMs').text(pingDuration+'ms'); pingDuration = Math.min(pingDuration, 255); var red = pingDuration; var green = 255 - pingDuration; var blue = 0; var hex = rgbToHex(red, green, blue); - $('#pingDot').css('color', '#' + hex); + $('#pingDot').css('color', '#'+hex); } else if (data == 'roundrestart') { opts.restarting = true; @@ -541,7 +542,7 @@ function ehjaxCallback(data) { dataJ = $.parseJSON(data); } catch (e) { //But...incorrect :sadtrombone: - window.onerror('JSON: ' + e + '. ' + data, 'browserOutput.html', 327); + window.onerror('JSON: '+e+'. '+data, 'browserOutput.html', 327); return; } data = dataJ; @@ -565,7 +566,7 @@ function ehjaxCallback(data) { adminMusic = adminMusic.match(/https?:\/\/\S+/) || ''; if (data.musicRate) { var newRate = Number(data.musicRate); - if (newRate) { + if(newRate) { $('#adminMusic').prop('defaultPlaybackRate', newRate); } } else { @@ -603,14 +604,14 @@ function ehjaxCallback(data) { function createPopup(contents, width) { opts.popups++; - $('body').append(''); + $('body').append(''); //Attach close popup event - var $popup = $('#popup' + opts.popups); + var $popup = $('#popup'+opts.popups); var height = $popup.outerHeight(); - $popup.css({ 'height': height + 'px', 'margin': '-' + (height / 2) + 'px 0 0 -' + (width / 2) + 'px' }); + $popup.css({'height': height+'px', 'margin': '-'+(height/2)+'px 0 0 -'+(width/2)+'px'}); - $popup.on('click', '.close', function (e) { + $popup.on('click', '.close', function(e) { e.preventDefault(); $popup.remove(); }); @@ -622,8 +623,8 @@ function toggleWasd(state) { function sendVolumeUpdate() { opts.volumeUpdating = false; - if (opts.updatedVolume) { - runByond('?_src_=chat&proc=setMusicVolume¶m[volume]=' + opts.updatedVolume); + if(opts.updatedVolume) { + runByond('?_src_=chat&proc=setMusicVolume¶m[volume]='+opts.updatedVolume); } } @@ -640,7 +641,7 @@ function adminMusicEndCheck(event) { } function adminMusicLoadedData(event) { - if (opts.musicStartAt && ($('#adminMusic').prop('duration') === Infinity || (opts.musicStartAt <= $('#adminMusic').prop('duration')))) { + if (opts.musicStartAt && ($('#adminMusic').prop('duration') === Infinity || (opts.musicStartAt <= $('#adminMusic').prop('duration'))) ) { $('#adminMusic').prop('currentTime', opts.musicStartAt); } if (opts.musicEndAt) { @@ -657,7 +658,7 @@ function startSubLoop() { if (opts.selectedSubLoop) { clearInterval(opts.selectedSubLoop); } - return setInterval(function () { + return setInterval(function() { if (!opts.suppressSubClose && $selectedSub.is(':visible')) { $selectedSub.slideUp('fast', subSlideUp); clearInterval(opts.selectedSubLoop); @@ -674,7 +675,7 @@ function handleToggleClick($sub, $toggle) { $selectedSub.slideUp('fast', subSlideUp); clearInterval(opts.selectedSubLoop); } else { - $selectedSub.slideDown('fast', function () { + $selectedSub.slideDown('fast', function() { var windowHeight = $(window).height(); var toggleHeight = $toggle.outerHeight(); var priorSubHeight = $selectedSub.outerHeight(); @@ -699,23 +700,23 @@ if (typeof $ === 'undefined') { div += '

ERROR: Jquery did not load.'; } -$(function () { +$(function() { $messages = $('#messages'); $subOptions = $('#subOptions'); $subAudio = $('#subAudio'); $selectedSub = $subOptions; //Hey look it's a controller loop! - setInterval(function () { + setInterval(function() { if (opts.lastPang + opts.pangLimit < Date.now() && !opts.restarting) { //Every pingLimit - if (!opts.noResponse) { //Only actually append a message if the previous ping didn't also fail (to prevent spam) - opts.noResponse = true; - opts.noResponseCount++; - internalOutput('
You are either AFK, experiencing lag or the connection has closed.
', 'internal'); - } + if (!opts.noResponse) { //Only actually append a message if the previous ping didn't also fail (to prevent spam) + opts.noResponse = true; + opts.noResponseCount++; + internalOutput('
You are either AFK, experiencing lag or the connection has closed.
', 'internal'); + } } else if (opts.noResponse) { //Previous ping attempt failed ohno - $('.connectionClosed[data-count="' + opts.noResponseCount + '"]:not(.restored)').addClass('restored').text('Your connection has been restored (probably)!'); - opts.noResponse = false; + $('.connectionClosed[data-count="'+opts.noResponseCount+'"]:not(.restored)').addClass('restored').text('Your connection has been restored (probably)!'); + opts.noResponse = false; } }, 2000); //2 seconds @@ -727,7 +728,6 @@ $(function () { ******************************************/ var savedConfig = { fontsize: getCookie('fontsize'), - lineheight: getCookie('lineheight'), 'spingDisabled': getCookie('pingdisabled'), 'shighlightTerms': getCookie('highlightterms'), 'shighlightColor': getCookie('highlightcolor'), @@ -738,13 +738,9 @@ $(function () { if (savedConfig.fontsize) { $messages.css('font-size', savedConfig.fontsize); - internalOutput('Loaded font size setting of: ' + savedConfig.fontsize + '', 'internal'); + internalOutput('Loaded font size setting of: '+savedConfig.fontsize+'', 'internal'); } - if (savedConfig.lineheight) { - $("body").css('line-height', savedConfig.lineheight); - internalOutput('Loaded line height setting of: ' + savedConfig.lineheight + '', 'internal'); - } - if (savedConfig.sdarkmode == 'true') { + if(savedConfig.sdarkmode == 'true'){ swap(); } if (savedConfig.spingDisabled) { @@ -780,7 +776,7 @@ $(function () { sendVolumeUpdate(); //internalOutput('Loaded music volume of: '+savedConfig.smusicVolume+'', 'internal'); } - else { + else{ $('#adminMusic').prop('volume', opts.defaultMusicVolume / 100); } @@ -791,14 +787,14 @@ $(function () { opts.messageCombining = true; } } - (function () { + (function() { var dataCookie = getCookie('connData'); if (dataCookie) { var dataJ; try { dataJ = $.parseJSON(dataCookie); } catch (e) { - window.onerror('JSON ' + e + '. ' + dataCookie, 'browserOutput.html', 434); + window.onerror('JSON '+e+'. '+dataCookie, 'browserOutput.html', 434); return; } opts.clientData = dataJ; @@ -812,15 +808,15 @@ $(function () { * ******************************************/ - $('body').on('click', 'a', function (e) { + $('body').on('click', 'a', function(e) { e.preventDefault(); }); - $('body').on('mousedown', function (e) { + $('body').on('mousedown', function(e) { var $target = $(e.target); - if ($contextMenu && opts.hasOwnProperty('contextMenuTarget') && opts.contextMenuTarget) { - hideContextMenu(); + if ($contextMenu) { + $contextMenu.hide(); return false; } @@ -833,14 +829,14 @@ $(function () { } }); - $messages.on('mousedown', function (e) { + $messages.on('mousedown', function(e) { if ($selectedSub && $selectedSub.is(':visible')) { $selectedSub.slideUp('fast', subSlideUp); clearInterval(opts.selectedSubLoop); } }); - $('body').on('mouseup', function (e) { + $('body').on('mouseup', function(e) { if (!opts.preventFocus && (e.pageX >= opts.mouseDownX - opts.clickTolerance && e.pageX <= opts.mouseDownX + opts.clickTolerance) && (e.pageY >= opts.mouseDownY - opts.clickTolerance && e.pageY <= opts.mouseDownY + opts.clickTolerance) @@ -851,19 +847,19 @@ $(function () { } }); - $messages.on('click', 'a', function (e) { + $messages.on('click', 'a', function(e) { var href = $(this).attr('href'); $(this).addClass('visited'); - if (href[0] == '?' || (href.length >= 8 && href.substring(0, 8) == 'byond://')) { + if (href[0] == '?' || (href.length >= 8 && href.substring(0,8) == 'byond://')) { runByond(href); } else { href = escaper(href); - runByond('?action=openLink&link=' + href); + runByond('?action=openLink&link='+href); } }); //Fuck everything about this event. Will look into alternatives. - $('body').on('keydown', function (e) { + $('body').on('keydown', function(e) { if (e.target.nodeName == 'INPUT' || e.target.nodeName == 'TEXTAREA') { return; } @@ -924,7 +920,7 @@ $(function () { if (!e.shiftKey) { c = c.toLowerCase(); } - runByond('byond://winset?mapwindow.map.focus=true;mainwindow.input.text=' + c); + runByond('byond://winset?mapwindow.map.focus=true;mainwindow.input.text='+c); return false; } else { runByond('byond://winset?mapwindow.map.focus=true'); @@ -933,7 +929,7 @@ $(function () { }); //Mildly hacky fix for scroll issues on mob change (interface gets resized sometimes, messing up snap-scroll) - $(window).on('resize', function (e) { + $(window).on('resize', function(e) { if ($(this).height() !== opts.priorChatHeight) { $('body,html').scrollTop($messages.outerHeight()); opts.priorChatHeight = $(this).height(); @@ -947,60 +943,46 @@ $(function () { * ******************************************/ - $('body').on('click', '#newMessages', function (e) { + $('body').on('click', '#newMessages', function(e) { var messagesHeight = $messages.outerHeight(); $('body,html').scrollTop(messagesHeight); $('#newMessages').remove(); runByond('byond://winset?mapwindow.map.focus=true'); }); - $('#toggleOptions').click(function (e) { + $('#toggleOptions').click(function(e) { handleToggleClick($subOptions, $(this)); }); - $('#darkmodetoggle').click(function (e) { + $('#darkmodetoggle').click(function(e) { swap(); }); - $('#toggleAudio').click(function (e) { + $('#toggleAudio').click(function(e) { handleToggleClick($subAudio, $(this)); }); - $('.sub, .toggle').mouseenter(function () { + $('.sub, .toggle').mouseenter(function() { opts.suppressSubClose = true; }); - $('.sub, .toggle').mouseleave(function () { + $('.sub, .toggle').mouseleave(function() { opts.suppressSubClose = false; }); - $('#decreaseFont').click(function (e) { + $('#decreaseFont').click(function(e) { savedConfig.fontsize = Math.max(parseInt(savedConfig.fontsize || 13) - 1, 1) + 'px'; - $messages.css({ 'font-size': savedConfig.fontsize }); + $messages.css({'font-size': savedConfig.fontsize}); setCookie('fontsize', savedConfig.fontsize, 365); - internalOutput('Font size set to ' + savedConfig.fontsize + '', 'internal'); + internalOutput('Font size set to '+savedConfig.fontsize+'', 'internal'); }); - $('#increaseFont').click(function (e) { + $('#increaseFont').click(function(e) { savedConfig.fontsize = (parseInt(savedConfig.fontsize || 13) + 1) + 'px'; - $messages.css({ 'font-size': savedConfig.fontsize }); + $messages.css({'font-size': savedConfig.fontsize}); setCookie('fontsize', savedConfig.fontsize, 365); - internalOutput('Font size set to ' + savedConfig.fontsize + '', 'internal'); - }); - - $('#decreaseLineHeight').click(function (e) { - savedConfig.lineheight = Math.max(parseFloat(savedConfig.lineheight || 1.2) - 0.1, 0.1).toFixed(1); - $("body").css({ 'line-height': savedConfig.lineheight }); - setCookie('lineheight', savedConfig.lineheight, 365); - internalOutput('Line height set to ' + savedConfig.lineheight + '', 'internal'); - }); - - $('#increaseLineHeight').click(function (e) { - savedConfig.lineheight = (parseFloat(savedConfig.lineheight || 1.2) + 0.1).toFixed(1); - $("body").css({ 'line-height': savedConfig.lineheight }); - setCookie('lineheight', savedConfig.lineheight, 365); - internalOutput('Line height set to ' + savedConfig.lineheight + '', 'internal'); + internalOutput('Font size set to '+savedConfig.fontsize+'', 'internal'); }); - $('#togglePing').click(function (e) { + $('#togglePing').click(function(e) { if (opts.pingDisabled) { $('#ping').slideDown('fast'); opts.pingDisabled = false; @@ -1011,63 +993,74 @@ $(function () { setCookie('pingdisabled', (opts.pingDisabled ? 'true' : 'false'), 365); }); - $('#saveLog').click(function (e) { - // Requires IE 10+ to issue download commands. Just opening a popup - // window will cause Ctrl+S to save a blank page, ignoring innerHTML. - if (!window.Blob) { - output('This function is only supported on IE 10 and up. Upgrade if possible.', 'internal'); - return; - } + $('#saveLog').click(function(e) { + var date = new Date(); + var fname = 'Azure Peak Chat Log ' + + date.getFullYear() + '-' + + (date.getMonth() + 1 < 10 ? '0' : '') + (date.getMonth() + 1) + '-' + + (date.getDate() < 10 ? '0' : '') + date.getDate() + ' ' + + (date.getHours() < 10 ? '0' : '') + date.getHours() + + (date.getMinutes() < 10 ? '0' : '') + date.getMinutes() + + (date.getSeconds() < 10 ? '0' : '') + date.getSeconds() + + '.html'; $.ajax({ type: 'GET', url: 'browserOutput_white.css', - success: function (styleData) { - var blob = new Blob(['Chat Log', $messagelog.html(), '']); - - var fname = 'SS13 Chat Log'; - var date = new Date(), month = date.getMonth(), day = date.getDay(), hours = date.getHours(), mins = date.getMinutes(), secs = date.getSeconds(); - fname += ' ' + date.getFullYear() + '-' + (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day; - fname += ' ' + (hours < 10 ? '0' : '') + hours + (mins < 10 ? '0' : '') + mins + (secs < 10 ? '0' : '') + secs; - fname += '.html'; - - window.navigator.msSaveBlob(blob, fname); - } + success: function(styleData) { + var blob = new Blob([ + 'Vanderlin Chat Log', + $messages.html(), + '' + ], { type: 'text/html;charset=utf-8' }); + + if (window.navigator.msSaveBlob) { + window.navigator.msSaveBlob(blob, fname); + } else { + var link = document.createElement('a'); + link.href = URL.createObjectURL(blob); + link.download = fname; + link.click(); + URL.revokeObjectURL(link.href); + } + }, }); }); - $('#highlightTerm').click(function (e) { - if ($('.popup .highlightTerm').is(':visible')) { return; } + $('#highlightTerm').click(function(e) { + if ($('.popup .highlightTerm').is(':visible')) {return;} var termInputs = ''; for (var i = 0; i < opts.highlightLimit; i++) { - termInputs += '
'; + termInputs += '
'; } var popupContent = '
String Highlighting
' + '
' + - '
Choose up to ' + opts.highlightLimit + ' strings that will highlight the line when they appear in chat.
' + - '
' + - termInputs + - '
' + - '
' + - '
' + + '
Choose up to '+opts.highlightLimit+' strings that will highlight the line when they appear in chat.
' + + '
' + + termInputs + + '
' + + '
' + + '
' + '
'; createPopup(popupContent, 250); }); - $('body').on('keyup', '#highlightColor', function () { + $('body').on('keyup', '#highlightColor', function() { var color = $('#highlightColor').val(); color = color.trim(); if (!color || color.charAt(0) != '#') return; $('#highlightColor').css('background-color', color); }); - $('body').on('submit', '#highlightTermForm', function (e) { + $('body').on('submit', '#highlightTermForm', function(e) { e.preventDefault(); var count = 0; while (count < opts.highlightLimit) { - var term = $('#highlightTermInput' + count).val(); + var term = $('#highlightTermInput'+count).val(); if (term) { term = term.trim(); if (term === '') { @@ -1095,32 +1088,32 @@ $(function () { setCookie('highlightcolor', opts.highlightColor, 365); }); - $('#clearMessages').click(function () { + $('#clearMessages').click(function() { $messages.empty(); opts.messageCount = 0; }); - $('#musicVolumeSpan').hover(function () { + $('#musicVolumeSpan').hover(function() { $('#musicVolumeText').addClass('hidden'); $('#musicVolume').removeClass('hidden'); - }, function () { + }, function() { $('#musicVolume').addClass('hidden'); $('#musicVolumeText').removeClass('hidden'); }); - $('#musicVolume').change(function () { + $('#musicVolume').change(function() { var newVolume = $('#musicVolume').val(); newVolume = clamp(newVolume, 0, 100); $('#adminMusic').prop('volume', newVolume / 100); setCookie('musicVolume', newVolume, 365); opts.updatedVolume = newVolume; - if (!opts.volumeUpdating) { + if(!opts.volumeUpdating) { setTimeout(sendVolumeUpdate, opts.volumeUpdateDelay); opts.volumeUpdating = true; } }); - $('#toggleCombine').click(function (e) { + $('#toggleCombine').click(function(e) { opts.messageCombining = !opts.messageCombining; setCookie('messagecombining', (opts.messageCombining ? 'true' : 'false'), 365); }); diff --git a/code/modules/goonchat/browserassets/js/errorHandler.js b/code/modules/goonchat/browserassets/js/errorHandler.js new file mode 100644 index 00000000000..bc9511bd577 --- /dev/null +++ b/code/modules/goonchat/browserassets/js/errorHandler.js @@ -0,0 +1,51 @@ +(function(window, navigator) { + + var escaper = encodeURIComponent || escape; + + var triggerError = function(msg, url, line, col, error) { + window.onerror(msg, url, line, col, error); + }; + + /** + * Directs JS errors to a byond proc for logging + * + * @param string file Name of the logfile to dump errors in, do not prepend with data/ + * @param boolean overrideDefault True to prevent default JS errors (an big honking error prompt thing) + * @param function customSuppress Pass a function that returns true to prevent logging of a specific error + * @return boolean + */ + var attach = function(file, overrideDefault, customSuppress) { + overrideDefault = typeof overrideDefault === 'undefined' ? false : overrideDefault; + file = escaper(file); + + //Prevent debug logging for those using anything lower than IE 10 + var trident = navigator.userAgent.match(/Trident\/(\d)\.\d(?:;|$)/gi); + var msie = document.documentMode; + var suppressLogging = (msie && msie < 10) || (trident && parseInt(trident) < 6); + + //Ok enough is enough, this prevents A CERTAIN PLAYER (Studenterhue) from spamming the error logs with bullshit + if (!window.JSON) { + suppressLogging = true; + } + + window.onerror = function(msg, url, line, col, error) { + if (typeof customSuppress === 'function' && customSuppress(msg, url, line, col, error)) { + suppressLogging = true; + } + + if (!suppressLogging) { + var extra = !col ? '' : ' | column: ' + col; + extra += !error ? '' : ' | error: ' + error; + extra += !navigator.userAgent ? '' : ' | user agent: ' + navigator.userAgent; + var debugLine = 'Error: ' + msg + ' | url: ' + url + ' | line: ' + line + extra; + window.location = '?action=debugFileOutput&file=' + file + '&message=' + escaper(debugLine); + } + return overrideDefault; + }; + + return triggerError; + }; + + window.attachErrorHandler = attach; + +}(window, window.navigator)); diff --git a/code/modules/goonchat/browserassets/js/jquery.min.js b/code/modules/goonchat/browserassets/js/jquery.min.js index ab28a24729b..e31f3f1dcc7 100644 --- a/code/modules/goonchat/browserassets/js/jquery.min.js +++ b/code/modules/goonchat/browserassets/js/jquery.min.js @@ -1,4 +1,5 @@ /*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; -if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/
","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("