diff --git a/.travis.yml b/.travis.yml index a57c1fd19f2..72a40f36442 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ env: TO_WORLD_COUNT=207 FLYWAY_BUILD="5.2.4" NODE_VERSION=10 - RUST_G_VERSION="0.4.2a" + RUST_G_VERSION="0.4.2+a2" PATH=/opt/python/3.7.1/bin:$PATH SPACEMAN_DMM_VERSION="suite-1.4" matrix: diff --git a/code/_helpers/text.dm b/code/_helpers/text.dm index d866fb679ec..7584b5a1126 100644 --- a/code/_helpers/text.dm +++ b/code/_helpers/text.dm @@ -57,6 +57,17 @@ /proc/sanitizeSafe(var/input, var/max_length = MAX_MESSAGE_LEN, var/encode = 1, var/trim = 1, var/extra = 1) return sanitize(replace_characters(input, list(">"=" ","<"=" ", "\""="'")), max_length, encode, trim, extra) +/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"="", "/"="", "\\"="", "?"="", "%"="", "*"="", ":"="", "|"="", "\""="", "<"="", ">"="")) + #define NO_CHARS_DETECTED 0 #define SPACES_DETECTED 1 #define SYMBOLS_DETECTED 2 diff --git a/code/controllers/subsystems/assets.dm b/code/controllers/subsystems/assets.dm index 7111f0738ee..05122fcafe5 100644 --- a/code/controllers/subsystems/assets.dm +++ b/code/controllers/subsystems/assets.dm @@ -16,10 +16,11 @@ ..("C:[target_clients.len]") /datum/controller/subsystem/assets/Initialize(timeofday) - for(var/type in typesof(/datum/asset) - list(/datum/asset, /datum/asset/simple)) - var/datum/asset/A = new type() - A.register() - CHECK_TICK + for(var/type in typesof(/datum/asset)) + var/datum/asset/A = type + if (type != initial(A._abstract)) + get_asset_datum(type) + CHECK_TICK for (var/client/C in global.clients) handle_connect(C) diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm index d9777ce980e..63423127037 100644 --- a/code/modules/client/asset_cache.dm +++ b/code/modules/client/asset_cache.dm @@ -148,8 +148,12 @@ var/list/asset_datums = list() asset.send(client) +/datum/asset + var/_abstract = /datum/asset + /datum/asset/New() asset_datums[type] = src + register() /datum/asset/proc/register() return @@ -159,7 +163,8 @@ var/list/asset_datums = list() //If you don't need anything complicated. /datum/asset/simple - var/assets = list() + _abstract = /datum/asset/simple + var/list/assets = list() var/verify = FALSE /datum/asset/simple/register() @@ -169,6 +174,247 @@ var/list/asset_datums = list() /datum/asset/simple/send(client) send_asset_list(client,assets,verify) +/datum/asset/chem_master + var/list/bottle_sprites = list("bottle-1", "bottle-2", "bottle-3", "bottle-4") + var/max_pill_sprite = 20 + var/list/assets = list() + +/datum/asset/chem_master/register() + for (var/i = 1 to max_pill_sprite) + var/name = "pill[i].png" + register_asset(name, icon('icons/obj/chemical.dmi', "pill[i]")) + assets += name + + for (var/sprite in bottle_sprites) + var/name = "[sprite].png" + register_asset(name, icon('icons/obj/chemical.dmi', sprite)) + assets += name + +/datum/asset/chem_master/send(client) + send_asset_list(client, assets) + +/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/goonchat + children = list( + /datum/asset/simple/jquery, + /datum/asset/simple/goonchat, + /datum/asset/simple/fontawesome, + /datum/asset/spritesheet/goonchat + ) + +// spritesheet implementation +#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]" + dll_call(RUST_G, "file_write", generate_css(), fname) + register_asset(res_name, file(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 + + var/fname = "data/spritesheets/[name]_[size_id].png" + fcopy(size[SPRSZ_ICON], fname) + var/error = dll_call(RUST_G, "dmi_strip_metadata", fname) + if(length(error)) + crash_with("Failed to strip [name]_[size_id].png: [error]") + size[SPRSZ_STRIPPED] = icon(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 {""} + +#undef SPR_SIZE +#undef SPR_IDX +#undef SPRSZ_COUNT +#undef SPRSZ_ICON +#undef SPRSZ_STRIPPED + + +/datum/asset/spritesheet/simple + _abstract = /datum/asset/spritesheet/simple + var/list/assets + +/datum/asset/spritesheet/simple/register() + for (var/key in assets) + Insert(key, assets[key]) + ..() + +//Generates assets based on iconstates of a single icon +/datum/asset/simple/icon_states + _abstract = /datum/asset/simple/icon_states + var/icon + var/list/directions = list(SOUTH) + var/frame = 1 + var/movement_states = FALSE + var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png" + var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format + verify = FALSE +/datum/asset/simple/icon_states/register(_icon = icon) + for(var/icon_state_name in icon_states(_icon)) + for(var/direction in directions) + var/asset = icon(_icon, icon_state_name, direction, frame, movement_states) + if (!asset) + continue + asset = fcopy_rsc(asset) //dedupe + var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : "" + var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png") + if (generic_icon_names) + asset_name = "[generate_asset_name(asset)].png" + register_asset(asset_name, asset) + +/datum/asset/simple/icon_states/multiple_icons + _abstract = /datum/asset/simple/icon_states/multiple_icons + var/list/icons + +/datum/asset/simple/icon_states/multiple_icons/register() + for(var/i in icons) + ..(i) + +//DEFINITIONS FOR ASSET DATUMS START HERE. + +/datum/asset/simple/faction_icons + assets = list( + "faction_EPMC.png" = 'icons/misc/factions/ECFlogo.png', + "faction_Zeng.png" = 'icons/misc/factions/ZhenHulogo.png', + "faction_Zavod.png" = 'icons/misc/factions/Zavodlogo.png', + "faction_NT.png" = 'icons/misc/factions/NanoTrasenlogo.png', + "faction_Idris.png" = 'icons/misc/factions/Idrislogo.png', + "faction_Hepht.png" = 'icons/misc/factions/Hephaestuslogo.png', + "faction_unaffiliated.png" = 'icons/misc/factions/Unaffiliatedlogo.png' + ) + +/datum/asset/simple/jquery + verify = FALSE + assets = list( + "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js', + ) + +/datum/asset/simple/goonchat + verify = FALSE + assets = list( + "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js', + "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js', + "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', + "browserOutput_white.css" = 'code/modules/goonchat/browserassets/css/browserOutput_white.css' + ) + +/datum/asset/simple/fontawesome + verify = FALSE + assets = list( + "fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot', + "fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff', + "fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot', + "fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff', + "font-awesome.css" = 'html/font-awesome/css/all.min.css', + "v4shim.css" = 'html/font-awesome/css/v4-shims.min.css' + ) + /datum/asset/simple/misc assets = list( "search.js" = 'html/search.js', @@ -217,87 +463,19 @@ var/list/asset_datums = list() "vueui.css" = 'vueui/dist/app.css' ) -/datum/asset/chem_master - var/list/bottle_sprites = list("bottle-1", "bottle-2", "bottle-3", "bottle-4") - var/max_pill_sprite = 20 - var/list/assets = list() +// /datum/asset/simple/accents +// verify = FALSE -/datum/asset/chem_master/register() - for (var/i = 1 to max_pill_sprite) - var/name = "pill[i].png" - register_asset(name, icon('icons/obj/chemical.dmi', "pill[i]")) - assets += name +// /datum/asset/simple/accents/register() +// for(var/A in subtypesof(/datum/accent)) //yes we have to do this here, SSrecords isn't initialized yet +// var/datum/accent/accent = new A +// var/name = "[accent.tag_icon].png" +// assets[name] = icon('./icons/accent_tags.dmi', accent.tag_icon) +// ..() - for (var/sprite in bottle_sprites) - var/name = "[sprite].png" - register_asset(name, icon('icons/obj/chemical.dmi', sprite)) - assets += name +/datum/asset/spritesheet/goonchat + name = "chat" -/datum/asset/chem_master/send(client) - send_asset_list(client, assets) - -/datum/asset/simple/accents - verify = FALSE - -/datum/asset/simple/accents/register() - for(var/A in subtypesof(/datum/accent)) //yes we have to do this here, SSrecords isn't initialized yet - var/datum/accent/accent = new A - var/name = "[accent.tag_icon].png" - assets[name] = icon('./icons/accent_tags.dmi', accent.tag_icon) +/datum/asset/spritesheet/goonchat/register() + InsertAll(null, './icons/accent_tags.dmi') ..() - -/datum/asset/simple/faction_icons - assets = list( - "faction_EPMC.png" = 'icons/misc/factions/ECFlogo.png', - "faction_Zeng.png" = 'icons/misc/factions/ZhenHulogo.png', - "faction_Zavod.png" = 'icons/misc/factions/Zavodlogo.png', - "faction_NT.png" = 'icons/misc/factions/NanoTrasenlogo.png', - "faction_Idris.png" = 'icons/misc/factions/Idrislogo.png', - "faction_Hepht.png" = 'icons/misc/factions/Hephaestuslogo.png', - "faction_unaffiliated.png" = 'icons/misc/factions/Unaffiliatedlogo.png' - ) - -/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/goonchat - children = list( - /datum/asset/simple/jquery, - /datum/asset/simple/goonchat, - /datum/asset/simple/fontawesome - ) - -/datum/asset/simple/jquery - verify = FALSE - assets = list( - "jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js', - ) - -/datum/asset/simple/goonchat - verify = FALSE - assets = list( - "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js', - "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js', - "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css', - "browserOutput_white.css" = 'code/modules/goonchat/browserassets/css/browserOutput_white.css' - ) - -/datum/asset/simple/fontawesome - verify = FALSE - assets = list( - "fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot', - "fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff', - "fa-solid-900.eot" = 'html/font-awesome/webfonts/fa-solid-900.eot', - "fa-solid-900.woff" = 'html/font-awesome/webfonts/fa-solid-900.woff', - "font-awesome.css" = 'html/font-awesome/css/all.min.css', - "v4shim.css" = 'html/font-awesome/css/v4-shims.min.css' - ) diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index ae2cac05d64..9db9217fd6e 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -90,5 +90,3 @@ // Check code/modules/admin/verbs/antag-ooc.dm for definition client.add_aooc_if_necessary() - - simple_asset_ensure_is_sent(client, /datum/asset/simple/accents) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 3e9378ddfe1..b55b9846238 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -1175,7 +1175,8 @@ proc/is_blind(A) if(used_accent && speaking?.allow_accents) var/datum/accent/a = SSrecords.accents[used_accent] var/final_icon = a.tag_icon - return "" + var/datum/asset/spritesheet/S = get_asset_datum(/datum/asset/spritesheet/goonchat) + return S.icon_tag(final_icon) /mob/proc/flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = FALSE, visual = FALSE, type = /obj/screen/fullscreen/flash) for(var/mob/M in contents) diff --git a/html/changelogs/johnwildkins-spritesheets.yml b/html/changelogs/johnwildkins-spritesheets.yml new file mode 100644 index 00000000000..7d3e73949c7 --- /dev/null +++ b/html/changelogs/johnwildkins-spritesheets.yml @@ -0,0 +1,6 @@ +author: JohnWildkins + +delete-after: True + +changes: + - backend: "Large groups of images are now sent via spritesheets, improving performance gains. Currently used on goonchat accent icons." diff --git a/rust_g.dll b/rust_g.dll index 3fa136548f8..b3ae24623f4 100644 Binary files a/rust_g.dll and b/rust_g.dll differ