mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 10:12:45 +00:00
Update asset_cache to modern /tg/, and make TGUI compatible with it. CDN support, etc.
This commit is contained in:
@@ -12,8 +12,6 @@
|
||||
#define TGUI_WINDOW_LOADING 1
|
||||
/// Window is free and ready to receive data
|
||||
#define TGUI_WINDOW_READY 2
|
||||
/// Window is in use by a tgui datum
|
||||
#define TGUI_WINDOW_ACTIVE 3
|
||||
|
||||
/// Get a window id based on the provided pool index
|
||||
#define TGUI_WINDOW_ID(index) "tgui-window-[index]"
|
||||
|
||||
@@ -189,6 +189,9 @@
|
||||
entry += ":\n[text]"
|
||||
WRITE_LOG(diary, entry)
|
||||
|
||||
/proc/log_asset(text)
|
||||
WRITE_LOG(diary, "ASSET: [text]")
|
||||
|
||||
/proc/report_progress(var/progress_message)
|
||||
admin_notice("<span class='boldannounce'>[progress_message]</span>", R_DEBUG)
|
||||
to_world_log(progress_message)
|
||||
|
||||
@@ -138,6 +138,19 @@
|
||||
/proc/sanitize_old(var/t,var/list/repl_chars = list("\n"="#","\t"="#"))
|
||||
return html_encode(replace_characters(t,repl_chars))
|
||||
|
||||
|
||||
//Removes a few problematic characters
|
||||
/proc/sanitize_simple(t,list/repl_chars = list("\n"="#","\t"="#"))
|
||||
for(var/char in repl_chars)
|
||||
var/index = findtext(t, char)
|
||||
while(index)
|
||||
t = copytext(t, 1, index) + repl_chars[char] + copytext(t, index + length(char))
|
||||
index = findtext(t, char, index + length(char))
|
||||
return t
|
||||
|
||||
/proc/sanitize_filename(t)
|
||||
return sanitize_simple(t, list("\n"="", "\t"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"=""))
|
||||
|
||||
/*
|
||||
* Text searches
|
||||
*/
|
||||
|
||||
@@ -1571,6 +1571,14 @@ var/mob/dview/dview_mob = new
|
||||
/datum/proc/stack_trace(msg)
|
||||
CRASH(msg)
|
||||
|
||||
GLOBAL_REAL_VAR(list/stack_trace_storage)
|
||||
/proc/gib_stack_trace()
|
||||
stack_trace_storage = list()
|
||||
stack_trace()
|
||||
stack_trace_storage.Cut(1, min(3,stack_trace_storage.len))
|
||||
. = stack_trace_storage
|
||||
stack_trace_storage = null
|
||||
|
||||
// \ref behaviour got changed in 512 so this is necesary to replicate old behaviour.
|
||||
// If it ever becomes necesary to get a more performant REF(), this lies here in wait
|
||||
// #define REF(thing) (thing && istype(thing, /datum) && (thing:datum_flags & DF_USE_TAG) && thing:tag ? "[thing:tag]" : "\ref[thing]")
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
var/datum/pipe_recipe/recipe // pipe recipie selected for display/construction
|
||||
var/static/datum/pipe_recipe/first_atmos
|
||||
var/static/datum/pipe_recipe/first_disposal
|
||||
var/static/datum/asset/iconsheet/pipes/icon_assets
|
||||
var/static/datum/asset/spritesheet/pipes/icon_assets
|
||||
var/static/list/pipe_layers = list(
|
||||
"Regular" = PIPING_LAYER_REGULAR,
|
||||
"Supply" = PIPING_LAYER_SUPPLY,
|
||||
@@ -75,7 +75,7 @@
|
||||
/obj/item/weapon/pipe_dispenser/interact(mob/user)
|
||||
SetupPipes()
|
||||
if(!icon_assets)
|
||||
icon_assets = get_asset_datum(/datum/asset/iconsheet/pipes)
|
||||
icon_assets = get_asset_datum(/datum/asset/spritesheet/pipes)
|
||||
icon_assets.send(user)
|
||||
|
||||
var/list/lines = list()
|
||||
@@ -365,7 +365,7 @@
|
||||
if(_dir == p_dir && flipped == p_flipped)
|
||||
attrs += " class=\"linkOn\""
|
||||
if(icon_state)
|
||||
var/img_tag = icon_assets.icon_tag(icon_state, _dir)
|
||||
var/img_tag = icon_assets.icon_tag("[dirtext]-[icon_state]")
|
||||
return "<a href=\"?src=\ref[src];dir=[dirtext];flipped=[flipped]\" title=\"[title]\"[attrs]>[img_tag]</a>"
|
||||
else
|
||||
return "<a href=\"?src=\ref[src];dir=[dirtext];flipped=[flipped]\" title=\"[title]\"[attrs]>[noimg]</a>"
|
||||
|
||||
110
code/modules/asset_cache/asset_cache.dm
Normal file
110
code/modules/asset_cache/asset_cache.dm
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
Asset cache quick users guide:
|
||||
|
||||
Make a datum in asset_list_items.dm with your assets for your thing.
|
||||
Checkout asset_list.dm for the helper subclasses
|
||||
The simple subclass 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.
|
||||
|
||||
Note: If your code uses output() with assets you will need to call asset_flush on the client and wait for it to return before calling output(). You only need do this if .send(client) returned TRUE
|
||||
*/
|
||||
|
||||
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
|
||||
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
|
||||
|
||||
//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)
|
||||
return send_asset_list(client, list(asset_name))
|
||||
|
||||
/// Sends a list of assets to a client
|
||||
/// This proc will no longer block, use client.asset_flush() if you to need know when the client has all assets (such as for output()). (This is not required for browse() calls as they use the same message queue as asset sends)
|
||||
/// client - a client or mob
|
||||
/// asset_list - A list of asset filenames to be sent to the client.
|
||||
/// Returns TRUE if any assets were sent.
|
||||
/proc/send_asset_list(client/client, list/asset_list)
|
||||
if(!istype(client))
|
||||
if(ismob(client))
|
||||
var/mob/M = client
|
||||
if(M.client)
|
||||
client = M.client
|
||||
else
|
||||
return
|
||||
else
|
||||
return
|
||||
|
||||
var/list/unreceived = list()
|
||||
|
||||
for (var/asset_name in asset_list)
|
||||
var/datum/asset_cache_item/asset = SSassets.cache[asset_name]
|
||||
if (!asset)
|
||||
continue
|
||||
var/asset_file = asset.resource
|
||||
if (!asset_file)
|
||||
continue
|
||||
|
||||
var/asset_md5 = asset.md5
|
||||
if (client.sent_assets[asset_name] == asset_md5)
|
||||
continue
|
||||
unreceived[asset_name] = asset_md5
|
||||
|
||||
if (unreceived.len)
|
||||
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
|
||||
to_chat(client, "Sending Resources...")
|
||||
|
||||
for(var/asset in unreceived)
|
||||
var/datum/asset_cache_item/ACI
|
||||
if ((ACI = SSassets.cache[asset]))
|
||||
log_asset("Sending asset [asset] to client [client]")
|
||||
client << browse_rsc(ACI.resource, asset)
|
||||
|
||||
client.sent_assets |= unreceived
|
||||
addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
//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, filerate = 3)
|
||||
var/startingfilerate = filerate
|
||||
for(var/file in files)
|
||||
if (!client)
|
||||
break
|
||||
if (register_asset)
|
||||
register_asset(file, files[file])
|
||||
|
||||
if (send_asset(client, file))
|
||||
if (!(--filerate))
|
||||
filerate = startingfilerate
|
||||
client.asset_flush()
|
||||
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.
|
||||
//icons and virtual assets get copied to the dyn rsc before use
|
||||
/proc/register_asset(asset_name, asset)
|
||||
var/datum/asset_cache_item/ACI = new(asset_name, asset)
|
||||
|
||||
//this is technically never something that was supported and i want metrics on how often it happens if at all.
|
||||
if (SSassets.cache[asset_name])
|
||||
var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
|
||||
if (OACI.md5 != ACI.md5)
|
||||
stack_trace("ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]")
|
||||
else
|
||||
var/list/stacktrace = gib_stack_trace()
|
||||
log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]")
|
||||
SSassets.cache[asset_name] = ACI
|
||||
return ACI
|
||||
|
||||
/// Returns the url of the asset, currently this is just its name, here to allow further work cdn'ing assets.
|
||||
/// Can be given an asset as well, this is just a work around for buggy edge cases where two assets may have the same name, doesn't matter now, but it will when the cdn comes.
|
||||
/proc/get_asset_url(asset_name, asset = null)
|
||||
var/datum/asset_cache_item/ACI = SSassets.cache[asset_name]
|
||||
return ACI?.url
|
||||
|
||||
//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))]"
|
||||
|
||||
51
code/modules/asset_cache/asset_cache_client.dm
Normal file
51
code/modules/asset_cache/asset_cache_client.dm
Normal file
@@ -0,0 +1,51 @@
|
||||
|
||||
/// Process asset cache client topic calls for "asset_cache_confirm_arrival=[INT]"
|
||||
/client/proc/asset_cache_confirm_arrival(job_id)
|
||||
var/asset_cache_job = round(text2num(job_id))
|
||||
//because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit.
|
||||
if (asset_cache_job > 0 && asset_cache_job <= last_asset_job && !(completed_asset_jobs["[asset_cache_job]"]))
|
||||
completed_asset_jobs["[asset_cache_job]"] = TRUE
|
||||
last_completed_asset_job = max(last_completed_asset_job, asset_cache_job)
|
||||
else
|
||||
return asset_cache_job || TRUE
|
||||
|
||||
|
||||
/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING]
|
||||
/client/proc/asset_cache_preload_data(data)
|
||||
/*var/jsonend = findtextEx(data, "{{{ENDJSONDATA}}}")
|
||||
if (!jsonend)
|
||||
CRASH("invalid asset_cache_preload_data, no jsonendmarker")*/
|
||||
//var/json = html_decode(copytext(data, 1, jsonend))
|
||||
var/json = data
|
||||
var/list/preloaded_assets = json_decode(json)
|
||||
|
||||
for (var/preloaded_asset in preloaded_assets)
|
||||
if (copytext(preloaded_asset, findlasttext(preloaded_asset, ".")+1) in list("js", "jsm", "htm", "html"))
|
||||
preloaded_assets -= preloaded_asset
|
||||
continue
|
||||
sent_assets |= preloaded_assets
|
||||
|
||||
|
||||
/// Updates the client side stored html/json combo file used to keep track of what assets the client has between restarts/reconnects.
|
||||
/client/proc/asset_cache_update_json(verify = FALSE, list/new_assets = list())
|
||||
if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection
|
||||
return
|
||||
if (!islist(new_assets))
|
||||
new_assets = list("[new_assets]" = md5(SSassets.cache[new_assets]))
|
||||
|
||||
src << browse(json_encode(new_assets|sent_assets), "file=asset_data.json&display=0")
|
||||
|
||||
/// Blocks until all currently sending browser assets have been sent.
|
||||
/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
|
||||
/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
|
||||
/client/proc/asset_flush(timeout = 50)
|
||||
var/job = ++last_asset_job
|
||||
var/t = 0
|
||||
var/timeout_time = timeout
|
||||
src << browse({"<script>window.location.href="?asset_cache_confirm_arrival=[job]"</script>"}, "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
|
||||
23
code/modules/asset_cache/asset_cache_item.dm
Normal file
23
code/modules/asset_cache/asset_cache_item.dm
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* # asset_cache_item
|
||||
*
|
||||
* An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
|
||||
**/
|
||||
/datum/asset_cache_item
|
||||
var/name
|
||||
var/url
|
||||
var/md5
|
||||
var/resource
|
||||
|
||||
/datum/asset_cache_item/New(name, file)
|
||||
if (!isfile(file))
|
||||
file = fcopy_rsc(file)
|
||||
md5 = md5(file)
|
||||
if (!md5)
|
||||
md5 = md5(fcopy_rsc(file))
|
||||
if (!md5)
|
||||
CRASH("invalid asset sent to asset cache")
|
||||
log_world("asset cache unexpected success of second fcopy_rsc")
|
||||
src.name = name
|
||||
url = name
|
||||
resource = file
|
||||
260
code/modules/asset_cache/asset_list.dm
Normal file
260
code/modules/asset_cache/asset_list.dm
Normal file
@@ -0,0 +1,260 @@
|
||||
|
||||
//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()
|
||||
|
||||
/datum/asset/simple/register()
|
||||
for(var/asset_name in assets)
|
||||
assets[asset_name] = register_asset(asset_name, assets[asset_name])
|
||||
|
||||
/datum/asset/simple/send(client)
|
||||
. = send_asset_list(client, assets)
|
||||
|
||||
/datum/asset/simple/get_url_mappings()
|
||||
. = list()
|
||||
for (var/asset_name in assets)
|
||||
var/datum/asset_cache_item/ACI = assets[asset_name]
|
||||
if (!ACI)
|
||||
continue
|
||||
.[asset_name] = ACI.url
|
||||
|
||||
|
||||
// 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]
|
||||
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)
|
||||
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"
|
||||
. = send_asset_list(C, all)
|
||||
|
||||
/datum/asset/spritesheet/get_url_mappings()
|
||||
if (!name)
|
||||
return
|
||||
. = list("spritesheet_[name].css" = get_asset_url("spritesheet_[name].css"))
|
||||
for(var/size_id in sizes)
|
||||
.["[name]_[size_id].png"] = 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
|
||||
|
||||
#ifdef RUST_G
|
||||
// save flattened version
|
||||
var/fname = "data/spritesheets/[name]_[size_id].png"
|
||||
fcopy(size[SPRSZ_ICON], fname)
|
||||
var/error = call(RUST_G, "dmi_strip_metadata")(fname)
|
||||
if(length(error))
|
||||
stack_trace("Failed to strip [name]_[size_id].png: [error]")
|
||||
size[SPRSZ_STRIPPED] = icon(fname)
|
||||
fdel(fname)
|
||||
#else
|
||||
#warn It looks like you don't have RUST_G enabled. Without RUST_G, the RPD icons will not function, so it strongly recommended you reenable it.
|
||||
#endif
|
||||
|
||||
/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('[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 {"<link rel="stylesheet" href="[css_filename()]" />"}
|
||||
|
||||
/datum/asset/spritesheet/proc/css_filename()
|
||||
return 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 {"<span class="[name][size_id] [sprite_name]"></span>"}
|
||||
|
||||
/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"
|
||||
|
||||
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)
|
||||
|
||||
|
||||
445
code/modules/asset_cache/asset_list_items.dm
Normal file
445
code/modules/asset_cache/asset_list_items.dm
Normal file
@@ -0,0 +1,445 @@
|
||||
//DEFINITIONS FOR ASSET DATUMS START HERE.
|
||||
|
||||
/datum/asset/simple/tgui
|
||||
assets = list(
|
||||
"tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js',
|
||||
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
|
||||
)
|
||||
|
||||
// /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',
|
||||
// "borg_mon.gif" = 'icons/program_icons/borg_mon.gif'
|
||||
// )
|
||||
|
||||
// /datum/asset/simple/radar_assets
|
||||
// assets = list(
|
||||
// "ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png',
|
||||
// "ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png',
|
||||
// "ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png'
|
||||
// )
|
||||
|
||||
// /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',
|
||||
// "skills" = 'icons/pda_icons/pda_skills.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',
|
||||
// "stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png',
|
||||
// "stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png',
|
||||
// "stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png',
|
||||
// "stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.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/generic
|
||||
assets = list(
|
||||
"search.js" = 'html/search.js',
|
||||
"panels.css" = 'html/panels.css',
|
||||
"loading.gif" = 'html/images/loading.gif',
|
||||
"ntlogo.png" = 'html/images/ntlogo.png',
|
||||
"sglogo.png" = 'html/images/sglogo.png',
|
||||
"talisman.png" = 'html/images/talisman.png',
|
||||
"paper_bg.png" = 'html/images/paper_bg.png',
|
||||
"no_image32.png" = 'html/images/no_image32.png',
|
||||
)
|
||||
|
||||
/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',
|
||||
"map-pencil.png" = 'html/map-pencil.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',
|
||||
"changelog.css" = 'html/changelog.css',
|
||||
"changelog.js" = 'html/changelog.js',
|
||||
"changelog.html" = 'html/changelog.html'
|
||||
)
|
||||
|
||||
// /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
|
||||
// assets = list(
|
||||
// "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js',
|
||||
// )
|
||||
|
||||
// /datum/asset/simple/goonchat
|
||||
// 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_white.css',
|
||||
// )
|
||||
|
||||
/datum/asset/simple/fontawesome
|
||||
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/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/simple/arcade
|
||||
// assets = list(
|
||||
// "boss1.gif" = 'icons/UI_Icons/Arcade/boss1.gif',
|
||||
// "boss2.gif" = 'icons/UI_Icons/Arcade/boss2.gif',
|
||||
// "boss3.gif" = 'icons/UI_Icons/Arcade/boss3.gif',
|
||||
// "boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif',
|
||||
// "boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif',
|
||||
// "boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif',
|
||||
// )
|
||||
|
||||
// /datum/asset/spritesheet/simple/achievements
|
||||
// name ="achievements"
|
||||
// assets = list(
|
||||
// "default" = 'icons/UI_Icons/Achievements/default.png',
|
||||
// "basemisc" = 'icons/UI_Icons/Achievements/basemisc.png',
|
||||
// "baseboss" = 'icons/UI_Icons/Achievements/baseboss.png',
|
||||
// "baseskill" = 'icons/UI_Icons/Achievements/baseskill.png',
|
||||
// "bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png',
|
||||
// "colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png',
|
||||
// "hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png',
|
||||
// "legion" = 'icons/UI_Icons/Achievements/Boss/legion.png',
|
||||
// "miner" = 'icons/UI_Icons/Achievements/Boss/miner.png',
|
||||
// "swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png',
|
||||
// "tendril" = 'icons/UI_Icons/Achievements/Boss/tendril.png',
|
||||
// "featofstrength" = 'icons/UI_Icons/Achievements/Misc/featofstrength.png',
|
||||
// "helbital" = 'icons/UI_Icons/Achievements/Misc/helbital.png',
|
||||
// "jackpot" = 'icons/UI_Icons/Achievements/Misc/jackpot.png',
|
||||
// "meteors" = 'icons/UI_Icons/Achievements/Misc/meteors.png',
|
||||
// "timewaste" = 'icons/UI_Icons/Achievements/Misc/timewaste.png',
|
||||
// "upgrade" = 'icons/UI_Icons/Achievements/Misc/upgrade.png',
|
||||
// "clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png',
|
||||
// "clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png',
|
||||
// "rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png',
|
||||
// "snail" = 'icons/UI_Icons/Achievements/Misc/snail.png',
|
||||
// "mining" = 'icons/UI_Icons/Achievements/Skills/mining.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',
|
||||
// )
|
||||
|
||||
// //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/pipe-item.dmi', 'icons/obj/pipes/disposal.dmi'))
|
||||
InsertAll("", each, global.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'
|
||||
// )
|
||||
|
||||
// /datum/asset/simple/orbit
|
||||
// assets = list(
|
||||
// "ghost.png" = 'html/ghost.png'
|
||||
// )
|
||||
|
||||
// /datum/asset/simple/vv
|
||||
// assets = list(
|
||||
// "view_variables.css" = 'html/admin/view_variables.css'
|
||||
// )
|
||||
|
||||
// /datum/asset/spritesheet/sheetmaterials
|
||||
// name = "sheetmaterials"
|
||||
|
||||
// /datum/asset/spritesheet/sheetmaterials/register()
|
||||
// InsertAll("", 'icons/obj/stack_objects.dmi')
|
||||
|
||||
// // Special case to handle Bluespace Crystals
|
||||
// Insert("polycrystal", 'icons/obj/telescience.dmi', "polycrystal")
|
||||
// ..()
|
||||
|
||||
/datum/asset/nanoui
|
||||
var/list/common = list()
|
||||
|
||||
var/list/common_dirs = list(
|
||||
"nano/css/",
|
||||
"nano/images/",
|
||||
"nano/images/modular_computers/",
|
||||
"nano/js/"
|
||||
)
|
||||
var/list/template_dirs = list(
|
||||
"nano/templates/"
|
||||
)
|
||||
|
||||
/datum/asset/nanoui/register()
|
||||
// Crawl the directories to find files.
|
||||
for(var/path in common_dirs)
|
||||
var/list/filenames = flist(path)
|
||||
for(var/filename in filenames)
|
||||
if(copytext(filename, length(filename)) != "/") // Ignore directories.
|
||||
if(fexists(path + filename))
|
||||
common[filename] = fcopy_rsc(path + filename)
|
||||
register_asset(filename, common[filename])
|
||||
// Combine all templates into a single bundle.
|
||||
var/list/template_data = list()
|
||||
for(var/path in template_dirs)
|
||||
var/list/filenames = flist(path)
|
||||
for(var/filename in filenames)
|
||||
if(copytext(filename, length(filename) - 4) == ".tmpl") // Ignore directories.
|
||||
template_data[filename] = file2text(path + filename)
|
||||
var/template_bundle = "function nanouiTemplateBundle(){return [json_encode(template_data)];}"
|
||||
var/fname = "data/nano_templates_bundle.js"
|
||||
fdel(fname)
|
||||
text2file(template_bundle, fname)
|
||||
register_asset("nano_templates_bundle.js", fcopy_rsc(fname))
|
||||
fdel(fname)
|
||||
|
||||
/datum/asset/nanoui/send(client)
|
||||
send_asset_list(client, common)
|
||||
29
code/modules/asset_cache/validate_assets.html
Normal file
29
code/modules/asset_cache/validate_assets.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
//this is used over window.location because window.location has a character limit in IE.
|
||||
function sendbyond(text) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '?'+text, true);
|
||||
xhr.send(null);
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'asset_data.json', true);
|
||||
xhr.responseType = 'text';
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
var status = xhr.status;
|
||||
if (status >= 200 && status < 400) {
|
||||
sendbyond('asset_cache_preload_data=' + encodeURIComponent(xhr.responseText));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,326 +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(var/client/client, var/asset_name, var/verify = TRUE)
|
||||
client = CLIENT_FROM_VAR(client) // Will get client from a mob, or accept a client, or return null
|
||||
if(!istype(client))
|
||||
return 0
|
||||
|
||||
if(client.cache.Find(asset_name) || client.sending.Find(asset_name))
|
||||
return 0
|
||||
|
||||
client << browse_rsc(SSassets.cache[asset_name], asset_name)
|
||||
if(!verify) // Can't access the asset cache browser, rip.
|
||||
client.cache += asset_name
|
||||
return 1
|
||||
|
||||
client.sending |= asset_name
|
||||
var/job = ++client.last_asset_job
|
||||
|
||||
client << browse({"
|
||||
<script>
|
||||
window.location.href="?asset_cache_confirm_arrival=[job]"
|
||||
</script>
|
||||
"}, "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()
|
||||
sleep(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(var/client/client, var/list/asset_list, var/verify = TRUE)
|
||||
client = CLIENT_FROM_VAR(client) // Will get client from a mob, or accept a client, or return null
|
||||
if(!istype(client))
|
||||
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)
|
||||
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({"
|
||||
<script>
|
||||
window.location.href="?asset_cache_confirm_arrival=[job]"
|
||||
</script>
|
||||
"}, "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()
|
||||
sleep(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(var/client/client, var/list/files, var/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)
|
||||
sleep(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(var/asset_name, var/asset)
|
||||
SSassets.cache[asset_name] = asset
|
||||
|
||||
//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
|
||||
/var/global/list/asset_datums = list()
|
||||
|
||||
//get a assetdatum or make a new one
|
||||
/proc/get_asset_datum(var/type)
|
||||
if(!(type in asset_datums))
|
||||
return new type()
|
||||
return asset_datums[type]
|
||||
|
||||
/datum/asset
|
||||
var/_abstract = /datum/asset // Marker so we don't instanatiate abstract types
|
||||
|
||||
/datum/asset/New()
|
||||
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)
|
||||
|
||||
/datum/asset/simple/tgui
|
||||
assets = list(
|
||||
"tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js',
|
||||
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css'
|
||||
)
|
||||
|
||||
//
|
||||
// iconsheet Assets - For making lots of icon states available at once without sending a thousand tiny files.
|
||||
//
|
||||
/datum/asset/iconsheet
|
||||
_abstract = /datum/asset/iconsheet
|
||||
var/name // Name of the iconsheet. Asset will be named after this.
|
||||
var/verify = FALSE
|
||||
|
||||
/datum/asset/iconsheet/register(var/list/sprites)
|
||||
if (!name)
|
||||
CRASH("iconsheet [type] cannot register without a name")
|
||||
if (!islist(sprites))
|
||||
CRASH("iconsheet [type] cannot register without a sprites list")
|
||||
|
||||
var/res_name = "iconsheet_[name].css"
|
||||
var/fname = "data/iconsheets/[res_name]"
|
||||
fdel(fname)
|
||||
text2file(generate_css(sprites), fname)
|
||||
register_asset(res_name, fcopy_rsc(fname))
|
||||
fdel(fname)
|
||||
|
||||
/datum/asset/iconsheet/send(client/C)
|
||||
if (!name)
|
||||
return
|
||||
send_asset_list(C, list("iconsheet_[name].css"), verify)
|
||||
|
||||
/datum/asset/iconsheet/proc/generate_css(var/list/sprites)
|
||||
var/list/out = list(".[name]{display:inline-block;}")
|
||||
for(var/sprite_id in sprites)
|
||||
var/icon/I = sprites[sprite_id]
|
||||
var/data_url = "'data:image/png;base64,[icon2base64(I)]'"
|
||||
out += ".[name].[sprite_id]{width:[I.Width()]px;height:[I.Height()]px;background-image:url([data_url]);}"
|
||||
return out.Join("\n")
|
||||
|
||||
/datum/asset/iconsheet/proc/build_sprite_list(icon/I, list/directions, prefix = null)
|
||||
if (length(prefix))
|
||||
prefix = "[prefix]-"
|
||||
|
||||
if (!directions)
|
||||
directions = list(SOUTH)
|
||||
|
||||
var/sprites = list()
|
||||
for (var/icon_state_name in cached_icon_states(I))
|
||||
for (var/direction in directions)
|
||||
var/suffix = (directions.len > 1) ? "-[dir2text(direction)]" : ""
|
||||
var/sprite_name = "[prefix][icon_state_name][suffix]"
|
||||
var/icon/sprite = icon(I, icon_state=icon_state_name, dir=direction, frame=1, moving=FALSE)
|
||||
if (!sprite || !length(cached_icon_states(sprite))) // that direction or state doesn't exist
|
||||
continue
|
||||
sprites[sprite_name] = sprite
|
||||
return sprites
|
||||
|
||||
// Get HTML link tag for including the iconsheet css file.
|
||||
/datum/asset/iconsheet/proc/css_tag()
|
||||
return "<link rel='stylesheet' href='iconsheet_[name].css' />"
|
||||
|
||||
// get HTML tag for showing an icon
|
||||
/datum/asset/iconsheet/proc/icon_tag(icon_state, dir = SOUTH)
|
||||
return "<span class='[name] [icon_state]-[dir2text(dir)]'></span>"
|
||||
|
||||
//DEFINITIONS FOR ASSET DATUMS START HERE.
|
||||
/datum/asset/simple/generic
|
||||
assets = list(
|
||||
"search.js" = 'html/search.js',
|
||||
"panels.css" = 'html/panels.css',
|
||||
"loading.gif" = 'html/images/loading.gif',
|
||||
"ntlogo.png" = 'html/images/ntlogo.png',
|
||||
"sglogo.png" = 'html/images/sglogo.png',
|
||||
"talisman.png" = 'html/images/talisman.png',
|
||||
"paper_bg.png" = 'html/images/paper_bg.png',
|
||||
"no_image32.png" = 'html/images/no_image32.png',
|
||||
)
|
||||
|
||||
/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',
|
||||
"map-pencil.png" = 'html/map-pencil.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',
|
||||
"changelog.css" = 'html/changelog.css',
|
||||
"changelog.js" = 'html/changelog.js',
|
||||
"changelog.html" = 'html/changelog.html'
|
||||
)
|
||||
|
||||
/datum/asset/nanoui
|
||||
var/list/common = list()
|
||||
|
||||
var/list/common_dirs = list(
|
||||
"nano/css/",
|
||||
"nano/images/",
|
||||
"nano/images/modular_computers/",
|
||||
"nano/js/"
|
||||
)
|
||||
var/list/template_dirs = list(
|
||||
"nano/templates/"
|
||||
)
|
||||
|
||||
/datum/asset/nanoui/register()
|
||||
// Crawl the directories to find files.
|
||||
for(var/path in common_dirs)
|
||||
var/list/filenames = flist(path)
|
||||
for(var/filename in filenames)
|
||||
if(copytext(filename, length(filename)) != "/") // Ignore directories.
|
||||
if(fexists(path + filename))
|
||||
common[filename] = fcopy_rsc(path + filename)
|
||||
register_asset(filename, common[filename])
|
||||
// Combine all templates into a single bundle.
|
||||
var/list/template_data = list()
|
||||
for(var/path in template_dirs)
|
||||
var/list/filenames = flist(path)
|
||||
for(var/filename in filenames)
|
||||
if(copytext(filename, length(filename) - 4) == ".tmpl") // Ignore directories.
|
||||
template_data[filename] = file2text(path + filename)
|
||||
var/template_bundle = "function nanouiTemplateBundle(){return [json_encode(template_data)];}"
|
||||
var/fname = "data/nano_templates_bundle.js"
|
||||
fdel(fname)
|
||||
text2file(template_bundle, fname)
|
||||
register_asset("nano_templates_bundle.js", fcopy_rsc(fname))
|
||||
fdel(fname)
|
||||
|
||||
/datum/asset/nanoui/send(client)
|
||||
send_asset_list(client, common)
|
||||
|
||||
// VOREStation Add Start - pipes iconsheet asset
|
||||
/datum/asset/iconsheet/pipes
|
||||
name = "pipes"
|
||||
|
||||
/datum/asset/iconsheet/pipes/register()
|
||||
var/list/sprites = list()
|
||||
for (var/each in list('icons/obj/pipe-item.dmi', 'icons/obj/pipes/disposal.dmi'))
|
||||
sprites += build_sprite_list(each, global.alldirs)
|
||||
..(sprites)
|
||||
// VOREStation Add End
|
||||
|
||||
// Fontawesome
|
||||
/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'
|
||||
)
|
||||
@@ -59,3 +59,18 @@
|
||||
preload_rsc = PRELOAD_RSC
|
||||
|
||||
var/global/obj/screen/click_catcher/void
|
||||
|
||||
// List of all asset filenames sent to this client by the asset cache, along with their assoicated md5s
|
||||
var/list/sent_assets = list()
|
||||
/// List of all completed blocking send jobs awaiting acknowledgement by send_asset
|
||||
var/list/completed_asset_jobs = list()
|
||||
/// Last asset send job id.
|
||||
var/last_asset_job = 0
|
||||
var/last_completed_asset_job = 0
|
||||
|
||||
///world.time they connected
|
||||
var/connection_time
|
||||
///world.realtime they connected
|
||||
var/connection_realtime
|
||||
///world.timeofday they connected
|
||||
var/connection_timeofday
|
||||
|
||||
@@ -34,10 +34,12 @@
|
||||
|
||||
#endif
|
||||
|
||||
// asset_cache
|
||||
var/asset_cache_job
|
||||
if(href_list["asset_cache_confirm_arrival"])
|
||||
var/job = text2num(href_list["asset_cache_confirm_arrival"])
|
||||
completed_asset_jobs += job
|
||||
return
|
||||
asset_cache_job = asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"])
|
||||
if (!asset_cache_job)
|
||||
return
|
||||
|
||||
//search the href for script injection
|
||||
if( findtext(href,"<script",1,0) )
|
||||
@@ -110,6 +112,15 @@
|
||||
if(config && config.log_hrefs && href_logfile)
|
||||
WRITE_LOG(href_logfile, "[src] (usr:[usr])</small> || [hsrc ? "[hsrc] " : ""][href]")
|
||||
|
||||
//byond bug ID:2256651
|
||||
if (asset_cache_job && (asset_cache_job in completed_asset_jobs))
|
||||
to_chat(src, "<span class='danger'>An error has been detected in how your client is receiving resources. Attempting to correct.... (If you keep seeing these messages you might want to close byond and reconnect)</span>")
|
||||
src << browse("...", "window=asset_cache_browser")
|
||||
return
|
||||
if (href_list["asset_cache_preload_data"])
|
||||
asset_cache_preload_data(href_list["asset_cache_preload_data"])
|
||||
return
|
||||
|
||||
switch(href_list["_src_"])
|
||||
if("holder") hsrc = holder
|
||||
if("usr") hsrc = mob
|
||||
@@ -185,6 +196,10 @@
|
||||
. = ..() //calls mob.Login()
|
||||
prefs.sanitize_preferences()
|
||||
|
||||
connection_time = world.time
|
||||
connection_realtime = world.realtime
|
||||
connection_timeofday = world.timeofday
|
||||
|
||||
if(custom_event_msg && custom_event_msg != "")
|
||||
to_chat(src, "<h1 class='alert'>Custom Event</h1>")
|
||||
to_chat(src, "<h2 class='alert'>A custom event is taking place. OOC Info:</h2>")
|
||||
@@ -412,8 +427,12 @@
|
||||
//send resources to the client. It's here in its own proc so we can move it around easiliy if need be
|
||||
/client/proc/send_resources()
|
||||
spawn (10) //removing this spawn causes all clients to not get verbs.
|
||||
|
||||
//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)
|
||||
addtimer(CALLBACK(GLOBAL_PROC, /proc/getFilesSlow, src, SSassets.preload, FALSE), 5 SECONDS)
|
||||
|
||||
mob/proc/MayRespawn()
|
||||
return 0
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, tgui_id)
|
||||
ui = new(user, src, tgui_id, name)
|
||||
ui.autoupdate = TRUE
|
||||
ui.open()
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
var/status = STATUS_INTERACTIVE
|
||||
/// Topic state used to determine status/interactability.
|
||||
var/datum/tgui_state/state = null
|
||||
/// Asset data to be sent with every update
|
||||
var/list/asset_data
|
||||
// The map z-level to display.
|
||||
var/map_z_level = 1
|
||||
|
||||
@@ -84,11 +82,14 @@
|
||||
opened_at = world.time
|
||||
window.acquire_lock(src)
|
||||
if(!window.is_ready())
|
||||
window.initialize()
|
||||
window.initialize(inline_assets = list(
|
||||
get_asset_datum(/datum/asset/simple/tgui)
|
||||
))
|
||||
else
|
||||
window.send_message("ping")
|
||||
window.send_asset(get_asset_datum(/datum/asset/simple/fontawesome))
|
||||
for(var/datum/asset/asset in src_object.ui_assets(user))
|
||||
send_asset(asset)
|
||||
window.send_asset(asset)
|
||||
window.send_message("update", get_payload(
|
||||
with_data = TRUE,
|
||||
with_static_data = TRUE))
|
||||
@@ -143,14 +144,10 @@
|
||||
*
|
||||
* required asset datum/asset
|
||||
*/
|
||||
/datum/tgui/proc/send_asset(var/datum/asset/asset)
|
||||
if(!user.client)
|
||||
return
|
||||
// if(istype(asset, /datum/asset/spritesheet))
|
||||
// var/datum/asset/spritesheet/spritesheet = asset
|
||||
// LAZYINITLIST(asset_data)
|
||||
// LAZYADD(asset_data["styles"], list(spritesheet.css_filename()))
|
||||
asset.send(user)
|
||||
/datum/tgui/proc/send_asset(datum/asset/asset)
|
||||
if(!window)
|
||||
CRASH("send_asset() can only be called after open().")
|
||||
window.send_asset(asset)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -218,8 +215,6 @@
|
||||
var/static_data = with_static_data && src_object.tgui_static_data(user)
|
||||
if(static_data)
|
||||
json_data["static_data"] = static_data
|
||||
if(asset_data)
|
||||
json_data["assets"] = asset_data
|
||||
if(src_object.tgui_shared_states)
|
||||
json_data["shared"] = src_object.tgui_shared_states
|
||||
return json_data
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
var/datum/tgui/locked_by
|
||||
var/fatally_errored = FALSE
|
||||
var/message_queue
|
||||
var/sent_assets = list()
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -36,8 +37,10 @@
|
||||
* Initializes the window with a fresh page. Puts window into the "loading"
|
||||
* state. You can begin sending messages right after initializing. Messages
|
||||
* will be put into the queue until the window finishes loading.
|
||||
*
|
||||
* optional inline_assets list List of assets to inline into the html.
|
||||
*/
|
||||
/datum/tgui_window/proc/initialize()
|
||||
/datum/tgui_window/proc/initialize(inline_assets = list())
|
||||
log_tgui(client, "[id]/initialize")
|
||||
if(!client)
|
||||
return
|
||||
@@ -52,15 +55,23 @@
|
||||
else
|
||||
options += "titlebar=1;can_resize=1;"
|
||||
// Generate page html
|
||||
// TODO: Make this static
|
||||
var/html = SStgui.basehtml
|
||||
html = replacetextEx(html, "\[tgui:windowId]", id)
|
||||
// Send required assets
|
||||
var/datum/asset/asset
|
||||
asset = get_asset_datum(/datum/asset/simple/tgui)
|
||||
asset.send(client)
|
||||
asset = get_asset_datum(/datum/asset/simple/fontawesome)
|
||||
asset.send(client)
|
||||
// Process inline assets
|
||||
var/inline_styles = ""
|
||||
var/inline_scripts = ""
|
||||
for(var/datum/asset/asset in inline_assets)
|
||||
var/mappings = asset.get_url_mappings()
|
||||
for(var/name in mappings)
|
||||
var/url = mappings[name]
|
||||
// Not urlencoding since asset strings are considered safe
|
||||
if(copytext(name, -4) == ".css")
|
||||
inline_styles += "<link rel=\"stylesheet\" type=\"text/css\" href=\"[url]\">\n"
|
||||
else if(copytext(name, -3) == ".js")
|
||||
inline_scripts += "<script type=\"text/javascript\" defer src=\"[url]\"></script>\n"
|
||||
asset.send()
|
||||
html = replacetextEx(html, "<!-- tgui:styles -->\n", inline_styles)
|
||||
html = replacetextEx(html, "<!-- tgui:scripts -->\n", inline_scripts)
|
||||
// Open the window
|
||||
client << browse(html, "window=[id];[options]")
|
||||
// Instruct the client to signal UI when the window is closed.
|
||||
@@ -88,7 +99,7 @@
|
||||
&& pooled \
|
||||
&& pool_index > 0 \
|
||||
&& pool_index <= TGUI_WINDOW_SOFT_LIMIT \
|
||||
&& status >= TGUI_WINDOW_READY
|
||||
&& status == TGUI_WINDOW_READY
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -109,6 +120,9 @@
|
||||
* Release the window lock.
|
||||
*/
|
||||
/datum/tgui_window/proc/release_lock()
|
||||
// Clean up assets sent by tgui datum which requested the lock
|
||||
if(locked)
|
||||
sent_assets = list()
|
||||
locked = FALSE
|
||||
locked_by = null
|
||||
|
||||
@@ -128,8 +142,7 @@
|
||||
send_message("suspend")
|
||||
return
|
||||
log_tgui(client, "[id]/close")
|
||||
locked = FALSE
|
||||
locked_by = null
|
||||
release_lock()
|
||||
status = TGUI_WINDOW_CLOSED
|
||||
message_queue = null
|
||||
// Do not close the window to give user some time
|
||||
@@ -159,13 +172,30 @@
|
||||
// Pack for sending via output()
|
||||
message = url_encode(message)
|
||||
// Place into queue if window is still loading
|
||||
if(!force && status == TGUI_WINDOW_LOADING)
|
||||
if(!force && status != TGUI_WINDOW_READY)
|
||||
if(!message_queue)
|
||||
message_queue = list()
|
||||
message_queue += list(message)
|
||||
return
|
||||
client << output(message, "[id].browser:update")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Makes an asset available to use in tgui.
|
||||
*
|
||||
* required asset datum/asset
|
||||
*/
|
||||
/datum/tgui_window/proc/send_asset(datum/asset/asset)
|
||||
if(!client || !asset)
|
||||
return
|
||||
// if(istype(asset, /datum/asset/spritesheet))
|
||||
// var/datum/asset/spritesheet/spritesheet = asset
|
||||
// send_message("asset/stylesheet", spritesheet.css_filename())
|
||||
send_message("asset/mappings", asset.get_url_mappings())
|
||||
sent_assets += list(asset)
|
||||
asset.send(client)
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
@@ -186,6 +216,11 @@
|
||||
/datum/tgui_window/proc/on_message(type, list/payload, list/href_list)
|
||||
switch(type)
|
||||
if("ready")
|
||||
// Status can be READY if user has refreshed the window.
|
||||
if(status == TGUI_WINDOW_READY)
|
||||
// Resend the assets
|
||||
for(var/asset in sent_assets)
|
||||
send_asset(asset)
|
||||
status = TGUI_WINDOW_READY
|
||||
if("log")
|
||||
if(href_list["fatal"])
|
||||
|
||||
@@ -92,8 +92,8 @@ const sendRawMessage = msg => {
|
||||
socket.send(json);
|
||||
}
|
||||
else {
|
||||
// Keep only 10 latest messages in the queue
|
||||
if (queue.length > 10) {
|
||||
// Keep only 100 latest messages in the queue
|
||||
if (queue.length > 100) {
|
||||
queue.shift();
|
||||
}
|
||||
queue.push(json);
|
||||
|
||||
@@ -9,15 +9,46 @@ import { createLogger } from './logging';
|
||||
|
||||
const logger = createLogger('assets');
|
||||
|
||||
const loadedAssets = {
|
||||
styles: [],
|
||||
};
|
||||
const EXCLUDED_PATTERNS = [
|
||||
/v4shim/i,
|
||||
];
|
||||
|
||||
export const loadCSS = filename => {
|
||||
if (loadedAssets.styles.includes(filename)) {
|
||||
const loadedStyles = [];
|
||||
const loadedMappings = {};
|
||||
|
||||
export const loadCSS = url => {
|
||||
if (loadedStyles.includes(url)) {
|
||||
return;
|
||||
}
|
||||
loadedAssets.styles.push(filename);
|
||||
logger.log(`loading stylesheet '${filename}'`);
|
||||
fgLoadCSS(filename);
|
||||
loadedStyles.push(url);
|
||||
logger.log(`loading stylesheet '${url}'`);
|
||||
fgLoadCSS(url);
|
||||
};
|
||||
|
||||
export const resolveAsset = name => (
|
||||
loadedMappings[name] || name
|
||||
);
|
||||
|
||||
export const assetMiddleware = store => next => action => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'asset/stylesheet') {
|
||||
loadCSS(payload);
|
||||
return;
|
||||
}
|
||||
if (type === 'asset/mappings') {
|
||||
for (let name of Object.keys(payload)) {
|
||||
// Skip anything that matches excluded patterns
|
||||
if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
|
||||
continue;
|
||||
}
|
||||
const url = payload[name];
|
||||
const ext = name.split('.').pop();
|
||||
loadedMappings[name] = url;
|
||||
if (ext === 'css') {
|
||||
loadCSS(url);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
next(action);
|
||||
};
|
||||
|
||||
@@ -79,7 +79,6 @@ export const backendReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
config,
|
||||
assets: payload.assets || {},
|
||||
data,
|
||||
shared,
|
||||
visible,
|
||||
@@ -253,7 +252,6 @@ export const sendAct = (action, payload = {}) => {
|
||||
* },
|
||||
* },
|
||||
* data: any,
|
||||
* assets: any,
|
||||
* shared: any,
|
||||
* visible: boolean,
|
||||
* interactive: boolean,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Box, Icon, Tooltip } from '.';
|
||||
import { Component } from 'inferno';
|
||||
import { useBackend } from "../backend";
|
||||
import { resolveAsset } from '../assets';
|
||||
|
||||
export class NanoMap extends Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -30,7 +30,6 @@ import './styles/themes/syndicate.scss';
|
||||
import { perf } from 'common/perf';
|
||||
import { render } from 'inferno';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client';
|
||||
import { loadCSS } from './assets';
|
||||
import { backendUpdate, backendSuspendSuccess, selectBackend, sendMessage } from './backend';
|
||||
import { setupDrag } from './drag';
|
||||
import { logger } from './logging';
|
||||
@@ -96,8 +95,6 @@ const renderLayout = () => {
|
||||
if (initialRender) {
|
||||
initialRender = false;
|
||||
}
|
||||
// Load assets
|
||||
assets?.styles?.forEach(filename => loadCSS(filename));
|
||||
};
|
||||
|
||||
// Parse JSON and report all abnormal JSON strings coming from BYOND
|
||||
@@ -145,7 +142,7 @@ const setupApp = () => {
|
||||
if (type === 'update') {
|
||||
window.__ref__ = payload.config.ref;
|
||||
if (suspended) {
|
||||
logger.log('reinitializing to:', payload.config.ref);
|
||||
logger.log('resuming');
|
||||
initialRender = 'recycled';
|
||||
}
|
||||
// Backend update dispatches a store action
|
||||
@@ -162,7 +159,8 @@ const setupApp = () => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
logger.log('unhandled message', message);
|
||||
// Pass the message directly to the store
|
||||
store.dispatch(message);
|
||||
};
|
||||
|
||||
// Enable hot module reloading
|
||||
@@ -185,9 +183,6 @@ const setupApp = () => {
|
||||
}
|
||||
window.update(stateJson);
|
||||
}
|
||||
|
||||
// Dynamically load font-awesome from browser's cache
|
||||
loadCSS('font-awesome.css');
|
||||
};
|
||||
|
||||
// Setup a fatal error reporter
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button } from '../components';
|
||||
import { refocusLayout } from './Layout';
|
||||
@@ -57,14 +58,14 @@ export const NtosWindow = (props, context) => {
|
||||
<Box key={header.icon} inline mr={1}>
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={header.icon} />
|
||||
src={resolveAsset(header.icon)} />
|
||||
</Box>
|
||||
))}
|
||||
<Box inline>
|
||||
{PC_ntneticon && (
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_ntneticon} />
|
||||
src={resolveAsset(PC_ntneticon)} />
|
||||
)}
|
||||
</Box>
|
||||
{!!PC_showbatteryicon && PC_batteryicon && (
|
||||
@@ -72,7 +73,7 @@ export const NtosWindow = (props, context) => {
|
||||
{PC_batteryicon && (
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_batteryicon} />
|
||||
src={resolveAsset(PC_batteryicon)} />
|
||||
)}
|
||||
{PC_batterypercent && (
|
||||
PC_batterypercent
|
||||
@@ -83,7 +84,7 @@ export const NtosWindow = (props, context) => {
|
||||
<Box inline mr={1}>
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_apclinkicon} />
|
||||
src={resolveAsset(PC_apclinkicon)} />
|
||||
</Box>
|
||||
)}
|
||||
{!!PC_showexitprogram && (
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -218,14 +218,10 @@ Byond.topic({
|
||||
</script>
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" type="text/css" href="tgui.bundle.css">
|
||||
|
||||
<!-- This is processed in byond, so interfaces can override the
|
||||
html head if needed, for custom sheets of style etc. -->
|
||||
<!--customheadhtml-->
|
||||
<!-- tgui:styles -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="text/javascript" defer src="tgui.bundle.js"></script>
|
||||
<!-- tgui:scripts -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { backendMiddleware, backendReducer } from './backend';
|
||||
import { debugReducer } from './debug';
|
||||
import { hotKeyMiddleware } from './hotkeys';
|
||||
import { createLogger } from './logging';
|
||||
import { assetMiddleware } from './assets';
|
||||
|
||||
const logger = createLogger('store');
|
||||
|
||||
@@ -24,13 +25,13 @@ export const createStore = () => {
|
||||
}),
|
||||
]);
|
||||
const middleware = [
|
||||
process.env.NODE_ENV !== 'production' && loggingMiddleware,
|
||||
assetMiddleware,
|
||||
hotKeyMiddleware,
|
||||
backendMiddleware,
|
||||
];
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
middleware.push(loggingMiddleware);
|
||||
}
|
||||
return createReduxStore(reducer, applyMiddleware(...middleware));
|
||||
return createReduxStore(reducer,
|
||||
applyMiddleware(...middleware.filter(Boolean)));
|
||||
};
|
||||
|
||||
const loggingMiddleware = store => next => action => {
|
||||
|
||||
@@ -1681,6 +1681,11 @@
|
||||
#include "code\modules\assembly\signaler.dm"
|
||||
#include "code\modules\assembly\timer.dm"
|
||||
#include "code\modules\assembly\voice.dm"
|
||||
#include "code\modules\asset_cache\asset_cache.dm"
|
||||
#include "code\modules\asset_cache\asset_cache_client.dm"
|
||||
#include "code\modules\asset_cache\asset_cache_item.dm"
|
||||
#include "code\modules\asset_cache\asset_list.dm"
|
||||
#include "code\modules\asset_cache\asset_list_items.dm"
|
||||
#include "code\modules\awaymissions\bluespaceartillery.dm"
|
||||
#include "code\modules\awaymissions\corpse.dm"
|
||||
#include "code\modules\awaymissions\exile.dm"
|
||||
@@ -1731,7 +1736,6 @@
|
||||
#include "code\modules\catalogue\cataloguer.dm"
|
||||
#include "code\modules\catalogue\cataloguer_visuals.dm"
|
||||
#include "code\modules\catalogue\cataloguer_vr.dm"
|
||||
#include "code\modules\client\asset_cache.dm"
|
||||
#include "code\modules\client\client defines.dm"
|
||||
#include "code\modules\client\client procs.dm"
|
||||
#include "code\modules\client\client procs_vr.dm"
|
||||
|
||||
Reference in New Issue
Block a user