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