diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 48f54fd60f..1935dcc8a5 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -55,7 +55,7 @@ WRITE_LOG(GLOB.world_game_log, "ADMIN: DSAY: [text]") /proc/log_consent(text) - WRITE_LOG(GLOB.world_game_log,"CONSENT: [text]") + WRITE_LOG(GLOB.world_game_log, "CONSENT: [text]") /* All other items are public. */ /proc/log_game(text) @@ -66,6 +66,9 @@ if (CONFIG_GET(flag/log_virus)) WRITE_LOG(GLOB.world_virus_log, "VIRUS: [text]") +/proc/log_asset(text) + WRITE_LOG(GLOB.world_asset_log, "ASSET: [text]") + /proc/log_access(text) if (CONFIG_GET(flag/log_access)) WRITE_LOG(GLOB.world_game_log, "ACCESS: [text]") diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm index 65fd772dfe..3bce9c560a 100644 --- a/code/_globalvars/logging.dm +++ b/code/_globalvars/logging.dm @@ -28,6 +28,8 @@ GLOBAL_VAR(world_job_debug_log) GLOBAL_PROTECT(world_job_debug_log) GLOBAL_VAR(world_virus_log) GLOBAL_PROTECT(world_virus_log) +GLOBAL_VAR(world_asset_log) +GLOBAL_PROTECT(world_asset_log) GLOBAL_VAR(world_map_error_log) GLOBAL_PROTECT(world_map_error_log) GLOBAL_VAR(subsystem_log) diff --git a/code/controllers/subsystem/assets.dm b/code/controllers/subsystem/assets.dm index 7b6554bd3f..7285298283 100644 --- a/code/controllers/subsystem/assets.dm +++ b/code/controllers/subsystem/assets.dm @@ -6,17 +6,7 @@ SUBSYSTEM_DEF(assets) var/list/preload = list() /datum/controller/subsystem/assets/Initialize(timeofday) - - var/list/priority_assets = list( - /datum/asset/simple/oui_theme_nano, - /datum/asset/simple/goonchat - ) - - for(var/type in priority_assets) - var/datum/asset/A = new type() - A.register() - - for(var/type in typesof(/datum/asset) - (priority_assets | list(/datum/asset, /datum/asset/simple))) + for(var/type in typesof(/datum/asset)) var/datum/asset/A = type if (type != initial(A._abstract)) get_asset_datum(type) diff --git a/code/controllers/subsystem/minimaps.dm b/code/controllers/subsystem/minimaps.dm index 75de71ca96..c25036a47d 100644 --- a/code/controllers/subsystem/minimaps.dm +++ b/code/controllers/subsystem/minimaps.dm @@ -1,8 +1,8 @@ SUBSYSTEM_DEF(minimaps) name = "Minimaps" flags = SS_NO_FIRE - var/list/station_minimaps - var/datum/minimap_group/station_minimap + var/list/station_minimaps = list() + var/datum/minimap_group/station_minimap = null /datum/controller/subsystem/minimaps/Initialize() if(!CONFIG_GET(flag/minimaps_enabled)) @@ -12,9 +12,9 @@ SUBSYSTEM_DEF(minimaps) return ..() /datum/controller/subsystem/minimaps/proc/build_minimaps() - station_minimaps = list() for(var/z in SSmapping.levels_by_trait(ZTRAIT_STATION)) var/datum/space_level/SL = SSmapping.get_level(z) var/name = (SL.name == initial(SL.name))? "[z] - Station" : "[z] - [SL.name]" station_minimaps += new /datum/minimap(z, name = name) + station_minimap = new(station_minimaps, "Station") diff --git a/code/datums/browser.dm b/code/datums/browser.dm index 5b1df6cb68..96b2a2a294 100644 --- a/code/datums/browser.dm +++ b/code/datums/browser.dm @@ -97,20 +97,20 @@ [get_footer()] "} -/datum/browser/proc/open(use_onclose = 1) +/datum/browser/proc/open(use_onclose = TRUE) if(isnull(window_id)) //null check because this can potentially nuke goonchat WARNING("Browser [title] tried to open with a null ID") to_chat(user, "The [title] browser you tried to open failed a sanity check! Please report this on github!") return var/window_size = "" - if (width && height) + if(width && height) window_size = "size=[width]x[height];" - if (stylesheets.len) - send_asset_list(user, stylesheets, verify=FALSE) - if (scripts.len) - send_asset_list(user, scripts, verify=FALSE) + if(stylesheets.len) + send_asset_list(user, stylesheets) + if(scripts.len) + send_asset_list(user, scripts) user << browse(get_content(), "window=[window_id];[window_size][window_options]") - if (use_onclose) + if(use_onclose) setup_onclose() /datum/browser/proc/setup_onclose() @@ -157,7 +157,7 @@ close() //designed as a drop in replacement for alert(); functions the same. (outside of needing User specified) -/proc/tgalert(var/mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) +/proc/tgalert(mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000) if (!User) User = usr switch(askuser(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout)) diff --git a/code/game/world.dm b/code/game/world.dm index 1c6f3bceba..82384e9da6 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -121,6 +121,7 @@ GLOBAL_LIST(topic_status_cache) GLOB.world_game_log = "[GLOB.log_directory]/game.log" GLOB.world_virus_log = "[GLOB.log_directory]/virus.log" + GLOB.world_asset_log = "[GLOB.log_directory]/asset.log" GLOB.world_attack_log = "[GLOB.log_directory]/attack.log" GLOB.world_pda_log = "[GLOB.log_directory]/pda.log" GLOB.world_telecomms_log = "[GLOB.log_directory]/telecomms.log" diff --git a/code/modules/asset_cache/asset_cache.dm b/code/modules/asset_cache/asset_cache.dm new file mode 100644 index 0000000000..7a12e5e9f6 --- /dev/null +++ b/code/modules/asset_cache/asset_cache.dm @@ -0,0 +1,100 @@ +/* +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 + +//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))]" diff --git a/code/modules/asset_cache/asset_cache_client.dm b/code/modules/asset_cache/asset_cache_client.dm new file mode 100644 index 0000000000..50a5685ded --- /dev/null +++ b/code/modules/asset_cache/asset_cache_client.dm @@ -0,0 +1,58 @@ + +/client + var/list/sent_assets = list() // List of all asset filenames sent to this client by the asset cache, along with their assoicated md5s + var/list/completed_asset_jobs = list() /// List of all completed blocking send jobs awaiting acknowledgement by send_asset + + var/last_asset_job = 0 /// Last asset send job id. + var/last_completed_asset_job = 0 + +/// 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({""}, "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_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm new file mode 100644 index 0000000000..0e7d44a7ac --- /dev/null +++ b/code/modules/asset_cache/asset_cache_item.dm @@ -0,0 +1,21 @@ +/** + * # 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/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") + debug_world_log("asset cache unexpected success of second fcopy_rsc") + src.name = name + resource = file diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm new file mode 100644 index 0000000000..fe9859f238 --- /dev/null +++ b/code/modules/asset_cache/asset_list.dm @@ -0,0 +1,228 @@ + +//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/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) + register_asset(asset_name, assets[asset_name]) + +/datum/asset/simple/send(client) + . = send_asset_list(client, assets) + + +// 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) + +/datum/asset/spritesheet/register() + if (!name) + CRASH("spritesheet [type] cannot register without a name") + ensure_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) + + 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) + +/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 + +/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) diff --git a/code/modules/client/asset_cache.dm b/code/modules/asset_cache/asset_list_items.dm similarity index 54% rename from code/modules/client/asset_cache.dm rename to code/modules/asset_cache/asset_list_items.dm index de7cd1696f..13a1803188 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -1,399 +1,22 @@ -/* -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 - - 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) - 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 = "spritesheet_[name].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. - +/* uncomment this and delete the tgui def bellow this for the new tgui /datum/asset/simple/tgui assets = list( - // tgui + "tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js', + "tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css', + ) +*/ +/datum/asset/simple/tgui + assets = list( + // Old 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', + // Old TGUI compatability + "tgui-fallback.html" = 'tgui-next/packages/tgui/public/tgui-fallback.html', "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', @@ -434,8 +57,16 @@ GLOBAL_LIST_EMPTY(asset_datums) "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' ) - +/* uncomment if you're porting the new ntnet app +/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( @@ -464,6 +95,7 @@ GLOBAL_LIST_EMPTY(asset_datums) "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' @@ -483,51 +115,10 @@ GLOBAL_LIST_EMPTY(asset_datums) "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/spritesheet/simple/minesweeper - name = "minesweeper" - assets = list( - "1" = 'icons/UI_Icons/minesweeper_tiles/one.png', - "2" = 'icons/UI_Icons/minesweeper_tiles/two.png', - "3" = 'icons/UI_Icons/minesweeper_tiles/three.png', - "4" = 'icons/UI_Icons/minesweeper_tiles/four.png', - "5" = 'icons/UI_Icons/minesweeper_tiles/five.png', - "6" = 'icons/UI_Icons/minesweeper_tiles/six.png', - "7" = 'icons/UI_Icons/minesweeper_tiles/seven.png', - "8" = 'icons/UI_Icons/minesweeper_tiles/eight.png', - "empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png', - "flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png', - "hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png', - "mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png', - "minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.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', + //"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 @@ -573,23 +164,20 @@ GLOBAL_LIST_EMPTY(asset_datums) ) /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_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css', + "browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css', //dark theme, cit specific "browserOutput_light.css" = 'code/modules/goonchat/browserassets/css/browserOutput_light.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', @@ -630,6 +218,90 @@ GLOBAL_LIST_EMPTY(asset_datums) "none_button.png" = 'html/none_button.png', ) +/datum/asset/spritesheet/simple/minesweeper + name = "minesweeper" + assets = list( + "1" = 'icons/UI_Icons/minesweeper_tiles/one.png', + "2" = 'icons/UI_Icons/minesweeper_tiles/two.png', + "3" = 'icons/UI_Icons/minesweeper_tiles/three.png', + "4" = 'icons/UI_Icons/minesweeper_tiles/four.png', + "5" = 'icons/UI_Icons/minesweeper_tiles/five.png', + "6" = 'icons/UI_Icons/minesweeper_tiles/six.png', + "7" = 'icons/UI_Icons/minesweeper_tiles/seven.png', + "8" = 'icons/UI_Icons/minesweeper_tiles/eight.png', + "empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png', + "flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png', + "hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png', + "mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png', + "minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.png' + ) + +/* Port the app game thing +/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)) @@ -640,7 +312,7 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset/spritesheet/pipes name = "pipes" -/datum/asset/spritesheet/pipes/register() +/datum/asset/spritesheet/pipes/register() //we do not have chempipes 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')) InsertAll("", each, GLOB.alldirs) ..() @@ -650,7 +322,7 @@ GLOBAL_LIST_EMPTY(asset_datums) name = "design" /datum/asset/spritesheet/research_designs/register() - for (var/path in subtypesof(/datum/design)) + for(var/path in subtypesof(/datum/design)) var/datum/design/D = path var/icon_file diff --git a/code/modules/asset_cache/validate_assets.html b/code/modules/asset_cache/validate_assets.html new file mode 100644 index 0000000000..205a7f4dad --- /dev/null +++ b/code/modules/asset_cache/validate_assets.html @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index d575a53afa..39487efc54 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -36,29 +36,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(!usr || usr != mob) //stops us calling Topic for somebody else's client. Also helps prevent usr=null return - // asset_cache + var/asset_cache_job if(href_list["asset_cache_confirm_arrival"]) - var/job = text2num(href_list["asset_cache_confirm_arrival"]) - //because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us - // into letting append to a list without limit. - if (job && job <= last_asset_job && !(job in completed_asset_jobs)) - completed_asset_jobs += job + asset_cache_job = asset_cache_confirm_arrival(href_list["asset_cache_confirm_arrival"]) + if (!asset_cache_job) return - else if (job in completed_asset_jobs) //byond bug ID:2256651 - to_chat(src, "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)") - src << browse("...", "window=asset_cache_browser") - // Keypress passthrough - if(href_list["__keydown"]) - var/keycode = browser_keycode_to_byond(href_list["__keydown"]) - if(keycode) - keyDown(keycode) - return - if(href_list["__keyup"]) - var/keycode = browser_keycode_to_byond(href_list["__keyup"]) - if(keycode) - keyUp(keycode) - return - var/mtl = CONFIG_GET(number/minute_topic_limit) if (!holder && mtl) @@ -75,7 +57,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( topiclimiter[ADMINSWARNED_AT] = minute msg += " Administrators have been informed." log_game("[key_name(src)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") - message_admins("[ADMIN_LOOKUPFLW(src)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") + message_admins("[ADMIN_LOOKUPFLW(usr)] [ADMIN_KICK(usr)] Has hit the per-minute topic limit of [mtl] topic calls in a given game minute") to_chat(src, "[msg]") return @@ -96,6 +78,27 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( if(!(href_list["_src_"] == "chat" && href_list["proc"] == "ping" && LAZYLEN(href_list) == 2)) log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") + //byond bug ID:2256651 + if (asset_cache_job && (asset_cache_job in completed_asset_jobs)) + to_chat(src, "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)") + 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 + + // Keypress passthrough + if(href_list["__keydown"]) + var/keycode = browser_keycode_to_byond(href_list["__keydown"]) + if(keycode) + keyDown(keycode) + return + if(href_list["__keyup"]) + var/keycode = browser_keycode_to_byond(href_list["__keyup"]) + if(keycode) + keyUp(keycode) + return + // Admin PM if(href_list["priv_msg"]) cmd_admin_pm(href_list["priv_msg"],null) @@ -858,8 +861,14 @@ GLOBAL_LIST_EMPTY(external_rsc_urls) 'html/browser/playeroptions.css', ) 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) + #if (PRELOAD_RSC == 0) for (var/name in GLOB.vox_sounds) var/file = GLOB.vox_sounds[name] diff --git a/code/modules/client/verbs/minimap.dm b/code/modules/client/verbs/minimap.dm index 3d213dc210..3cdb1d57a8 100644 --- a/code/modules/client/verbs/minimap.dm +++ b/code/modules/client/verbs/minimap.dm @@ -6,5 +6,7 @@ if(!CONFIG_GET(flag/minimaps_enabled)) to_chat(usr, "Minimap generation is not enabled in the server's configuration.") return - + if(!SSminimaps.station_minimap) + to_chat(usr, "Minimap generation is in progress, please wait!") + return SSminimaps.station_minimap.show(src) diff --git a/code/modules/mapping/minimaps.dm b/code/modules/mapping/minimaps.dm index 29a9fca9b7..8410a53753 100644 --- a/code/modules/mapping/minimaps.dm +++ b/code/modules/mapping/minimaps.dm @@ -1,21 +1,27 @@ /datum/minimap - var/name + var/name = "minimap" + var/icon/overlay_icon + // The map icons var/icon/map_icon var/icon/meta_icon - var/icon/overlay_icon + var/list/color_area_names = list() + var/minx var/maxx var/miny var/maxy - var/z_level - var/id = 0 - var/static/next_id = 0 -/datum/minimap/New(z, x1 = 1, y1 = 1, x2 = world.maxx, y2 = world.maxy, name) + var/z_level + var/id = "" + +/datum/minimap/New(z, x1 = 1, y1 = 1, x2 = world.maxx, y2 = world.maxy, name = "minimap") + if(!z) + CRASH("ERROR: new minimap requested without z level") //CRASH to halt the operatio + src.name = name - id = ++next_id z_level = z + id = "[md5("[z_level]" + src.name + REF(src))]" //use it's own md5 as a special identifier var/crop_x1 = x2 var/crop_x2 = x1 @@ -25,10 +31,11 @@ // do the generating map_icon = new('html/blank.png') meta_icon = new('html/blank.png') - map_icon.Scale(x2-x1+1, y2-y1+1) // arrays start at 1 - meta_icon.Scale(x2-x1+1, y2-y1+1) + map_icon.Scale(x2 - x1 + 1, y2 - y1 + 1) // arrays start at 1 + meta_icon.Scale(x2 - x1 + 1, y2 - y1 + 1) + var/list/area_to_color = list() - for(var/turf/T in block(locate(x1,y1,z),locate(x2,y2,z))) + for(var/turf/T in block(locate(x1, y1, z_level), locate(x2, y2, z_level))) var/area/A = T.loc var/img_x = T.x - x1 + 1 // arrays start at 1 var/img_y = T.y - y1 + 1 @@ -37,21 +44,26 @@ crop_x2 = max(crop_x2, T.x) crop_y1 = min(crop_y1, T.y) crop_y2 = max(crop_y2, T.y) + var/meta_color = area_to_color[A] if(!meta_color) - meta_color = rgb(rand(0,255),rand(0,255),rand(0,255)) // technically conflicts could happen but it's like very unlikely and it's not that big of a deal if one happens + meta_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255)) // technically conflicts could happen but it's like very unlikely and it's not that big of a deal if one happens area_to_color[A] = meta_color color_area_names[meta_color] = A.name meta_icon.DrawBox(meta_color, img_x, img_y) + if(istype(T, /turf/closed/wall)) map_icon.DrawBox("#000000", img_x, img_y) + else if(!istype(A, /area/space)) var/color = A.minimap_color || "#FF00FF" if(locate(/obj/machinery/power/solar) in T) color = "#02026a" + if((locate(/obj/effect/spawner/structure/window) in T) || (locate(/obj/structure/grille) in T)) color = BlendRGB(color, "#000000", 0.5) map_icon.DrawBox(color, img_x, img_y) + map_icon.Crop(crop_x1, crop_y1, crop_x2, crop_y2) meta_icon.Crop(crop_x1, crop_y1, crop_x2, crop_y2) minx = crop_x1 @@ -60,14 +72,17 @@ maxy = crop_y2 overlay_icon = new(map_icon) overlay_icon.Scale(16, 16) - -/datum/minimap/proc/send(mob/user) + //we're done baking, now we ship it. register_asset("minimap-[id].png", map_icon) register_asset("minimap-[id]-meta.png", meta_icon) - send_asset_list(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon), verify=FALSE) + +/datum/minimap/proc/send(mob/user) + if(!id) + CRASH("ERROR: send called, but the minimap id is null/missing. ID: [id]") + send_asset_list(user, list("minimap-[id].png" = map_icon, "minimap-[id]-meta.png" = meta_icon)) /datum/minimap_group - var/list/minimaps + var/list/minimaps = list() var/static/next_id = 0 var/id var/name @@ -75,46 +90,61 @@ /datum/minimap_group/New(list/maps, name) id = ++next_id src.name = name - minimaps = maps || list() + minimaps = maps /datum/minimap_group/proc/show(mob/user) if(!length(minimaps)) to_chat(user, "ERROR: Attempted to access an empty datum/minimap_group. This should probably not happen.") return + var/list/datas = list() var/list/info = list() - var/datum/minimap/first_map = minimaps[1] - for(var/i in 1 to length(minimaps)) + + for(var/i in 1 to length(minimaps))// OLD: for(var/i in 1 to length(minimaps)) var/datum/minimap/M = minimaps[i] M.send(user) - info += "
" + info += {" +
+
+ + +
+
+
+ "} datas += json_encode(M.color_area_names); - info = info.Join() - var/html = {" - - - - - - - -[name] - -[info] -"} + + + "} - user << browse(html, "window=minimap_[id];size=768x[round(768 / first_map.map_icon.Width() * first_map.map_icon.Height() + 50)]") + var/datum/browser/popup = new(user, "minimap_[id]", name, 500, 700) + popup.add_head_content(headerJS) //set the head + popup.set_content(info) + var/datum/minimap/MICO = minimaps[1] + popup.set_title_image(MICO.overlay_icon) + popup.open(FALSE) diff --git a/code/modules/oracle_ui/assets.dm b/code/modules/oracle_ui/assets.dm index 5d26d80a81..348860b48d 100644 --- a/code/modules/oracle_ui/assets.dm +++ b/code/modules/oracle_ui/assets.dm @@ -1,8 +1,11 @@ +/datum/asset/group/oui_theme_nano + children = list( + /datum/asset/simple/jquery, + /datum/asset/simple/oui_theme_nano + ) + /datum/asset/simple/oui_theme_nano assets = list( - // JavaScript - "sui-nano-common.js" = 'html/oracle_ui/themes/nano/sui-nano-common.js', - "sui-nano-jquery.min.js" = 'html/oracle_ui/themes/nano/sui-nano-jquery.min.js', - // Stylesheets - "sui-nano-common.css" = 'html/oracle_ui/themes/nano/sui-nano-common.css', + "sui-nano-common.js" = 'html/oracle_ui/themes/nano/sui-nano-common.js', + "sui-nano-common.css" = 'html/oracle_ui/themes/nano/sui-nano-common.css', ) diff --git a/html/admin/view_variables.css b/html/admin/view_variables.css index 83b3a37f3c..34c1a211eb 100644 --- a/html/admin/view_variables.css +++ b/html/admin/view_variables.css @@ -30,6 +30,6 @@ table.matrixbrak td.lbrak { border-right: none; } table.matrixbrak td.rbrak { - border-right: solid 0.5exrgb(95, 61, 61)k; + border-right: solid 0.5ex black; border-left: none; } diff --git a/tgstation.dme b/tgstation.dme index 93958e5d96..1ee9cd16f8 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -1610,6 +1610,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\atmospherics\multiz.dm" #include "code\modules\atmospherics\environmental\LINDA_fire.dm" #include "code\modules\atmospherics\environmental\LINDA_system.dm" @@ -1752,7 +1757,6 @@ #include "code\modules\cargo\packs\service.dm" #include "code\modules\cargo\packs\vending.dm" #include "code\modules\chatter\chatter.dm" -#include "code\modules\client\asset_cache.dm" #include "code\modules\client\client_colour.dm" #include "code\modules\client\client_defines.dm" #include "code\modules\client\client_procs.dm"