diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm
index 521ce0d08e2..f102a96adfe 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_type=BROWSE_ROOT_ALL_LOGS, max_iterations=10, list/valid_extensions=list("txt","log","htm", "html"))
// wow why was this ever a parameter
var/root = "data/logs/"
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index bb46a9b6542..884c8efe0c5 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1103,6 +1103,14 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0
/// Save file used in icon2base64. Used for converting icons to base64.
GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of icons for the browser output
+
+/// 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.
@@ -1138,10 +1146,10 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico
if (isfile(thing)) //special snowflake
var/name = sanitize_filename("[generate_asset_name(thing)].png")
if (!SSassets.cache[name])
- register_asset(name, thing)
+ SSassets.transport.register_asset(name, thing)
for (var/thing2 in targets)
- send_asset(thing2, key)
- return ""
+ SSassets.transport.send_assets(thing2, name)
+ return ""
var/atom/A = thing
if (isnull(dir))
dir = A.dir
@@ -1163,11 +1171,11 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico
key = "[generate_asset_name(I)].png"
if(!SSassets.cache[key])
- register_asset(key, I)
+ SSassets.transport.register_asset(key, I)
for (var/thing2 in targets)
- send_asset(thing2, key)
+ SSassets.transport.send_assets(thing2, key)
- return ""
+ return ""
/proc/icon2base64html(thing)
if (!thing)
diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm
index 4873e35d0b3..ec4523ea27c 100644
--- a/code/__HELPERS/type2type.dm
+++ b/code/__HELPERS/type2type.dm
@@ -487,3 +487,8 @@ GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH,
return "turf"
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 {"
"}
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 4070964c330..be69d77a9cb 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -52,6 +52,9 @@
LoadMOTD()
LoadPolicy()
LoadChatFilter()
+
+ if (Master)
+ Master.OnConfigLoad()
/datum/controller/configuration/proc/full_wipe()
if(IsAdminAdvancedProcCall())
diff --git a/code/controllers/configuration/entries/resources.dm b/code/controllers/configuration/entries/resources.dm
new file mode 100644
index 00000000000..c839ccc078d
--- /dev/null
+++ b/code/controllers/configuration/entries/resources.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/controllers/master.dm b/code/controllers/master.dm
index d7fcda6c5aa..ffe29172f2e 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -633,3 +633,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 574ed33311c..d9b16624232 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -208,6 +208,8 @@
if(SS_SLEEPING)
state = SS_PAUSING
+/// Called after the config has been loaded or reloaded.
+/datum/controller/subsystem/proc/OnConfigLoad()
//used to initialize the subsystem AFTER the map has loaded
/datum/controller/subsystem/Initialize(start_timeofday)
diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm
index 7285298283c..4f02d32ad0f 100644
--- a/code/controllers/subsystem/assets.dm
+++ b/code/controllers/subsystem/assets.dm
@@ -4,6 +4,23 @@ 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 +28,6 @@ SUBSYSTEM_DEF(assets)
if (type != initial(A._abstract))
get_asset_datum(type)
- preload = cache.Copy() //don't preload assets generated during the round
+ transport.Initialize(cache)
- for(var/client/C in GLOB.clients)
- addtimer(CALLBACK(GLOBAL_PROC, .proc/getFilesSlow, C, preload, FALSE), 10)
..()
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index b18b23ddfe7..0d44759a6f3 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -8,11 +8,11 @@
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/static/datum/asset/simple/namespaced/common/common_asset = get_asset_datum(/datum/asset/simple/namespaced/common)
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null)
@@ -27,7 +27,6 @@
height = nheight
if (nref)
ref = nref
- add_stylesheet("common", 'html/browser/common.css') // this CSS sheet is common to all UIs
/datum/browser/proc/add_head_content(nhead_content)
head_content = nhead_content
@@ -35,9 +34,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
@@ -48,11 +44,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
@@ -62,15 +58,13 @@
/datum/browser/proc/get_header()
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 {"
@@ -81,7 +75,7 @@
- [title ? "
[title]
" : ""]
+ [title ? "
[title]
" : ""]
"}
//" This is here because else the rest of the file looks like a string in notepad++.
@@ -107,10 +101,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)
+ SSassets.transport.send_assets(user, stylesheets)
if (scripts.len)
- send_asset_list(user, scripts)
+ SSassets.transport.send_assets(user, scripts)
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
if (use_onclose)
setup_onclose()
@@ -419,12 +414,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/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index b7ff2fb3705..2e40d75c13a 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -277,7 +277,6 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
if(user.client) //mainly here to avoid a runtime when the player gets gibbed when losing the emag mode.
var/datum/browser/popup = new(user, "arcade", "Space Villain 2000")
popup.set_content(dat)
- popup.set_title_image(user.browse_rsc_icon(icon, icon_state))
popup.open()
@@ -844,7 +843,6 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list(
dat += "