diff --git a/code/__DEFINES/assets.dm b/code/__DEFINES/assets.dm
new file mode 100644
index 00000000000..deed966e219
--- /dev/null
+++ b/code/__DEFINES/assets.dm
@@ -0,0 +1,16 @@
+#define ASSET_CROSS_ROUND_CACHE_DIRECTORY "cache/assets"
+#define ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY "data/spritesheets/smart_cache"
+
+/// When sending mutiple assets, how many before we give the client a quaint little sending resources message
+#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
+
+/// How many assets can be sent at once during legacy asset transport
+#define SLOW_ASSET_SEND_RATE 6
+
+/// Constructs a universal icon. This is done in the same manner as the icon() BYOND proc.
+/// "color" will not do anything if a transform is provided. Blend it yourself or use color_transform().
+/// Do note that transforms are NOT COPIED, and are internally lists. So take care not to re-use transforms.
+/// This is a DEFINE for performance reasons.
+/// Parameters (in order):
+/// icon_file, icon_state, dir, frame, transform, color
+#define uni_icon(I, icon_state, rest...) new /datum/universal_icon(I, icon_state, ##rest)
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index acaacd24010..cee1a46c134 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -845,6 +845,21 @@
.[i] = key
.[key] = value
+/// A version of deep_copy_list that actually supports associative list nesting: list(list(list("a" = "b"))) will actually copy correctly.
+/proc/deep_copy_list_alt(list/inserted_list)
+ if(!islist(inserted_list))
+ return inserted_list
+ var/copied_list = inserted_list.Copy()
+ . = copied_list
+ for(var/key_or_value in inserted_list)
+ if(isnum(key_or_value) || !inserted_list[key_or_value])
+ continue
+ var/value = inserted_list[key_or_value]
+ var/new_value = value
+ if(islist(value))
+ new_value = deep_copy_list_alt(value)
+ copied_list[key_or_value] = new_value
+
///takes an input_key, as text, and the list of keys already used, outputting a replacement key in the format of "[input_key] ([number_of_duplicates])" if it finds a duplicate
///use this for lists of things that might have the same name, like mobs or objects, that you plan on giving to a player as input
/proc/avoid_assoc_duplicate_keys(input_key, list/used_key_list)
diff --git a/code/__HELPERS/files.dm b/code/__HELPERS/files.dm
index f8cb06fd762..b5890163bb8 100644
--- a/code/__HELPERS/files.dm
+++ b/code/__HELPERS/files.dm
@@ -111,6 +111,7 @@ GLOBAL_VAR_INIT(fileaccess_timer, 0)
/// Save file as an external file then md5 it.
/// Used because md5ing files stored in the rsc sometimes gives incorrect md5 results.
+/// https://www.byond.com/forum/post/2611357
/proc/md5asfile(file)
var/static/notch = 0
// its importaint this code can handle md5filepath sleeping instead of hard blocking, if it's converted to use rust_g.
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index d6ec26debba..5c84032d62d 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -704,6 +704,9 @@
/datum/config_entry/flag/cache_assets
default = TRUE
+/datum/config_entry/flag/smart_cache_assets
+ default = TRUE
+
/datum/config_entry/flag/save_spritesheets
default = FALSE
diff --git a/code/controllers/subsystem/asset_loading.dm b/code/controllers/subsystem/asset_loading.dm
index 2d2939de4b2..ecdf406894a 100644
--- a/code/controllers/subsystem/asset_loading.dm
+++ b/code/controllers/subsystem/asset_loading.dm
@@ -7,6 +7,7 @@ SUBSYSTEM_DEF(asset_loading)
flags = SS_NO_INIT
runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT
var/list/datum/asset/generate_queue = list()
+ var/last_queue_len = 0
/datum/controller/subsystem/asset_loading/fire(resumed)
while(length(generate_queue))
@@ -16,7 +17,12 @@ SUBSYSTEM_DEF(asset_loading)
if(MC_TICK_CHECK)
return
+ last_queue_len = length(generate_queue)
generate_queue.len--
+ // We just emptied the queue
+ if(last_queue_len && !length(generate_queue))
+ // Clean up cached icons, freeing memory.
+ rustg_iconforge_cleanup()
/datum/controller/subsystem/asset_loading/proc/queue_asset(datum/asset/queue)
#ifdef DO_NOT_DEFER_ASSETS
diff --git a/code/controllers/subsystem/processing/greyscale.dm b/code/controllers/subsystem/processing/greyscale.dm
index 0c0db7b4f70..294cce8cc91 100644
--- a/code/controllers/subsystem/processing/greyscale.dm
+++ b/code/controllers/subsystem/processing/greyscale.dm
@@ -43,6 +43,16 @@ PROCESSING_SUBSYSTEM_DEF(greyscale)
CRASH("Invalid colors were given to `GetColoredIconByType()`: [colors]")
return configurations[type].Generate(colors)
+/datum/controller/subsystem/processing/greyscale/proc/GetColoredIconByTypeUniversalIcon(type, list/colors, target_icon_state)
+ if(!ispath(type, /datum/greyscale_config))
+ CRASH("An invalid greyscale configuration was given to `GetColoredIconByTypeUniversalIcon()`: [type]")
+ type = "[type]"
+ if(istype(colors)) // It's the color list format
+ colors = colors.Join()
+ else if(!istext(colors))
+ CRASH("Invalid colors were given to `GetColoredIconByTypeUniversalIcon()`: [colors]")
+ return configurations[type].GenerateUniversalIcon(colors, target_icon_state)
+
/datum/controller/subsystem/processing/greyscale/proc/ParseColorString(color_string)
. = list()
var/list/split_colors = splittext(color_string, "#")
diff --git a/code/datums/achievements/_achievement_data.dm b/code/datums/achievements/_achievement_data.dm
index eeaa5b6edee..c8ca0abea87 100644
--- a/code/datums/achievements/_achievement_data.dm
+++ b/code/datums/achievements/_achievement_data.dm
@@ -76,7 +76,7 @@
/datum/achievement_data/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/simple/achievements),
+ get_asset_datum(/datum/asset/spritesheet_batched/achievements),
)
/datum/achievement_data/ui_state(mob/user)
@@ -96,7 +96,7 @@
.["progresses"] = list()
.["user_key"] = owner_ckey
- var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)
+ var/datum/asset/spritesheet_batched/assets = get_asset_datum(/datum/asset/spritesheet_batched/achievements)
for(var/achievement_type in SSachievements.awards)
var/datum/award/award = SSachievements.awards[achievement_type]
if(!award.name) //No name? we a subtype.
diff --git a/code/datums/browser.dm b/code/datums/browser.dm
index cf0df8dac50..8027a2438f2 100644
--- a/code/datums/browser.dm
+++ b/code/datums/browser.dm
@@ -40,6 +40,9 @@
if (istype(name, /datum/asset/spritesheet))
var/datum/asset/spritesheet/sheet = name
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
+ else if (istype(name, /datum/asset/spritesheet_batched))
+ var/datum/asset/spritesheet_batched/sheet = name
+ stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
else
var/asset_name = "[name].css"
diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm
index 04a53ce3a3a..103265845ef 100644
--- a/code/datums/components/crafting/crafting.dm
+++ b/code/datums/components/crafting/crafting.dm
@@ -504,7 +504,7 @@
var/static/list/sprite_sheets
if(isnull(sprite_sheets))
sprite_sheets = ui_assets()
- var/datum/asset/spritesheet/sheet = sprite_sheets[mode ? 2 : 1]
+ var/datum/asset/spritesheet_batched/sheet = sprite_sheets[mode ? 2 : 1]
data["icon_data"] = list()
for(var/atom/atom as anything in atoms)
@@ -581,8 +581,8 @@
/datum/component/personal_crafting/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/crafting),
- get_asset_datum(/datum/asset/spritesheet/crafting/cooking),
+ get_asset_datum(/datum/asset/spritesheet_batched/crafting),
+ get_asset_datum(/datum/asset/spritesheet_batched/crafting/cooking),
)
/datum/component/personal_crafting/proc/build_crafting_data(datum/crafting_recipe/recipe)
diff --git a/code/datums/greyscale/_greyscale_config.dm b/code/datums/greyscale/_greyscale_config.dm
index ddfda189ef2..83fcd6250aa 100644
--- a/code/datums/greyscale/_greyscale_config.dm
+++ b/code/datums/greyscale/_greyscale_config.dm
@@ -306,4 +306,43 @@
output["icon"] = GenerateBundle(colors, debug_steps)
return output
+// ===============
+// Universal Icons
+// ===============
+
+/datum/greyscale_config/proc/GenerateUniversalIcon(color_string, target_bundle_state, datum/universal_icon/last_external_icon)
+ return GenerateBundleUniversalIcon(color_string, target_bundle_state, last_external_icon=last_external_icon)
+
+/// Handles the actual icon manipulation to create the spritesheet
+/datum/greyscale_config/proc/GenerateBundleUniversalIcon(list/colors, target_bundle_state, datum/universal_icon/last_external_icon)
+ if(!istype(colors))
+ colors = SSgreyscale.ParseColorString(colors)
+ if(length(colors) != expected_colors)
+ CRASH("[DebugName()] expected [expected_colors] color arguments but received [length(colors)]")
+
+ if(!(target_bundle_state in icon_states))
+ CRASH("Invalid target bundle icon_state \"[target_bundle_state]\"! Valid icon_states: [icon_states.Join(", ")]")
+
+ var/datum/universal_icon/icon_bundle = GenerateLayerGroupUniversalIcon(colors, icon_states[target_bundle_state], last_external_icon) || uni_icon('icons/effects/effects.dmi', "nothing")
+ icon_bundle.scale(width, height)
+ return icon_bundle
+
+/// Internal recursive proc to handle nested layer groups
+/datum/greyscale_config/proc/GenerateLayerGroupUniversalIcon(list/colors, list/group, datum/universal_icon/last_external_icon)
+ var/datum/universal_icon/new_icon
+ for(var/datum/greyscale_layer/layer as anything in group)
+ var/datum/universal_icon/layer_icon
+ if(islist(layer))
+ layer_icon = GenerateLayerGroupUniversalIcon(colors, layer, new_icon || last_external_icon)
+ var/list/layer_list = layer
+ layer = layer_list[1] // When there are multiple layers in a group like this we use the first one's blend mode
+ else
+ layer_icon = layer.GenerateUniversalIcon(colors, new_icon || last_external_icon)
+
+ if(!new_icon)
+ new_icon = layer_icon
+ else
+ new_icon.blend_icon(layer_icon, layer.blend_mode)
+ return new_icon
+
#undef MAX_SANE_LAYERS
diff --git a/code/datums/greyscale/layer.dm b/code/datums/greyscale/layer.dm
index 6512a6c3d56..aa3bc32e447 100644
--- a/code/datums/greyscale/layer.dm
+++ b/code/datums/greyscale/layer.dm
@@ -76,16 +76,34 @@
var/icon/copy_of_new_icon = icon(new_icon) // Layers shouldn't be modifying it directly, this is just for them to reference
return InternalGenerate(processed_colors, render_steps, copy_of_new_icon)
+/// Used to actualy create the layer using the given colors
+/// Do not override, use InternalGenerate instead
+/datum/greyscale_layer/proc/GenerateUniversalIcon(list/colors, datum/universal_icon/new_icon)
+ var/list/processed_colors = list()
+ for(var/i in color_ids)
+ if(isnum(i))
+ processed_colors += colors[i]
+ else
+ processed_colors += i
+ var/datum/universal_icon/copy_of_new_icon = isnull(new_icon) ? uni_icon('icons/effects/effects.dmi', "nothing") : new_icon.copy() // Layers shouldn't be modifying it directly, this is just for them to reference
+ return InternalGenerateUniversalIcon(processed_colors, copy_of_new_icon)
+
/// Override this to implement layers.
/// The colors var will only contain colors that this layer is configured to use.
/datum/greyscale_layer/proc/InternalGenerate(list/colors, list/render_steps, icon/new_icon)
+/// Override this to implement layers.
+/// The colors var will only contain colors that this layer is configured to use.
+/datum/greyscale_layer/proc/InternalGenerateUniversalIcon(list/colors, datum/universal_icon/new_icon)
+ return new_icon
+
////////////////////////////////////////////////////////
// Subtypes
/// The most basic greyscale layer; a layer which is created from a single icon_state in the given icon file
/datum/greyscale_layer/icon_state
layer_type = "icon_state"
+ var/icon_file
var/icon_state
var/icon/icon
var/color_id
@@ -94,6 +112,7 @@
. = ..()
if(!icon_exists(icon_file, icon_state))
CRASH("Configured icon state \[[icon_state]\] was not found in [icon_file]. Double check your json configuration.")
+ src.icon_file = icon_file
icon = new(icon_file, icon_state)
if(length(color_ids) > 1)
@@ -110,6 +129,13 @@
generated_icon.Blend(colors[1], ICON_MULTIPLY)
return generated_icon
+/datum/greyscale_layer/icon_state/InternalGenerateUniversalIcon(list/colors, datum/universal_icon/new_icon)
+ . = ..()
+ var/datum/universal_icon/generated_icon = uni_icon(icon_file, icon_state)
+ if(length(colors))
+ generated_icon.blend_color(colors[1], ICON_MULTIPLY)
+ return generated_icon
+
/// A layer to modify the previous layer's colors with a color matrix
/datum/greyscale_layer/color_matrix
layer_type = "color_matrix"
@@ -153,3 +179,8 @@
else
generated_icon = reference_type.Generate(colors.Join(), new_icon)
return icon(generated_icon, icon_state)
+
+/datum/greyscale_layer/reference/InternalGenerateUniversalIcon(list/colors, datum/universal_icon/new_icon)
+ var/datum/universal_icon/generated_icon = reference_type.GenerateUniversalIcon(colors.Join(), icon_state, new_icon)
+ generated_icon = generated_icon.copy()
+ return generated_icon
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 85db1e34e6d..b4ce13ecf89 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -139,7 +139,7 @@
var/list/output = list()
- var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
+ var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
for(var/design_id in designs)
@@ -191,8 +191,8 @@
/obj/machinery/autolathe/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
- get_asset_datum(/datum/asset/spritesheet/research_designs),
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs),
)
/obj/machinery/autolathe/ui_data(mob/user)
diff --git a/code/game/machinery/computer/arcade/orion.dm b/code/game/machinery/computer/arcade/orion.dm
index 5c2ed9b6d46..728eb682735 100644
--- a/code/game/machinery/computer/arcade/orion.dm
+++ b/code/game/machinery/computer/arcade/orion.dm
@@ -143,7 +143,7 @@
/obj/machinery/computer/arcade/orion_trail/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/moods),
+ get_asset_datum(/datum/asset/spritesheet_batched/moods),
)
/obj/machinery/computer/arcade/orion_trail/ui_data(mob/user)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index b562713fe37..a27fa80e497 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -1914,6 +1914,7 @@
name = "freezer airlock"
icon = 'icons/obj/doors/airlocks/station/freezer.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_fre
+ can_be_glass = FALSE
/obj/machinery/door/airlock/science
name = "science airlock"
@@ -2269,6 +2270,7 @@
icon = 'icons/obj/doors/airlocks/centcom/centcom.dmi'
overlays_file = 'icons/obj/doors/airlocks/centcom/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_centcom
+ can_be_glass = FALSE
normal_integrity = 1000
security_level = 6
explosion_block = 2
@@ -2277,6 +2279,7 @@
icon = 'icons/obj/doors/airlocks/centcom/centcom.dmi'
overlays_file = 'icons/obj/doors/airlocks/centcom/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_grunge
+ can_be_glass = FALSE
// Vault Airlocks
@@ -2286,6 +2289,7 @@
icon = 'icons/obj/doors/airlocks/vault/vault.dmi'
overlays_file = 'icons/obj/doors/airlocks/vault/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_vault
+ can_be_glass = FALSE
explosion_block = 2
normal_integrity = 400 // reverse engieneerd: 400 * 1.5 (sec lvl 6) = 600 = original
security_level = 6
@@ -2299,6 +2303,7 @@
overlays_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
note_overlay_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_hatch
+ can_be_glass = FALSE
/obj/machinery/door/airlock/maintenance_hatch
name = "maintenance hatch"
@@ -2306,6 +2311,7 @@
overlays_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
note_overlay_file = 'icons/obj/doors/airlocks/hatch/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_mhatch
+ can_be_glass = FALSE
// High Security Airlocks
@@ -2314,6 +2320,7 @@
icon = 'icons/obj/doors/airlocks/highsec/highsec.dmi'
overlays_file = 'icons/obj/doors/airlocks/highsec/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_highsecurity
+ can_be_glass = FALSE
explosion_block = 2
normal_integrity = 500
security_level = 1
@@ -2337,6 +2344,7 @@
icon = 'icons/obj/doors/airlocks/abductor/abductor_airlock.dmi'
overlays_file = 'icons/obj/doors/airlocks/abductor/overlays.dmi'
assemblytype = /obj/structure/door_assembly/door_assembly_abductor
+ can_be_glass = FALSE
note_overlay_file = 'icons/obj/doors/airlocks/external/overlays.dmi'
damage_deflection = 30
explosion_block = 3
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 67605ac1446..6c0b205797b 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -29,6 +29,8 @@
var/visible = TRUE
var/operating = FALSE
var/glass = FALSE
+ /// If something isn't a glass door but doesn't have a fill_closed icon (no glass slots), this prevents it from being used
+ var/can_be_glass = TRUE
/// Do we need to keep track of a filler panel with the airlock
var/multi_tile
/// A filler object used to fill the space of multi-tile airlocks
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 3f113d33189..671fdef666b 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -790,7 +790,7 @@
/obj/machinery/door/firedoor/heavy
name = "heavy firelock"
- icon = 'icons/obj/doors/Doorfire.dmi'
+ icon = 'icons/obj/doors/doorfire.dmi'
glass = FALSE
explosion_block = 2
assemblytype = /obj/structure/firelock_frame/heavy
@@ -805,7 +805,7 @@
/obj/structure/firelock_frame
name = "firelock frame"
desc = "A partially completed firelock."
- icon = 'icons/obj/doors/Doorfire.dmi'
+ icon = 'icons/obj/doors/doorfire.dmi'
icon_state = "frame1"
base_icon_state = "frame"
anchored = FALSE
diff --git a/code/game/machinery/flatpacker.dm b/code/game/machinery/flatpacker.dm
index d6751732533..d39d0a936ac 100644
--- a/code/game/machinery/flatpacker.dm
+++ b/code/game/machinery/flatpacker.dm
@@ -212,8 +212,8 @@
/obj/machinery/flatpacker/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
- get_asset_datum(/datum/asset/spritesheet/research_designs),
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs),
)
/obj/machinery/flatpacker/ui_static_data(mob/user)
diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm
index 1b3197e702d..29f2b8e16ed 100644
--- a/code/game/machinery/telecomms/computers/message.dm
+++ b/code/game/machinery/telecomms/computers/message.dm
@@ -270,7 +270,7 @@
/obj/machinery/computer/message_monitor/ui_assets(mob/user)
. = ..()
- . += get_asset_datum(/datum/asset/spritesheet/chat)
+ . += get_asset_datum(/datum/asset/spritesheet_batched/chat)
#undef MSG_MON_SCREEN_MAIN
#undef MSG_MON_SCREEN_LOGS
diff --git a/code/game/machinery/telecomms/computers/telemonitor.dm b/code/game/machinery/telecomms/computers/telemonitor.dm
index e70c7f7de17..ff7b10dbd0f 100644
--- a/code/game/machinery/telecomms/computers/telemonitor.dm
+++ b/code/game/machinery/telecomms/computers/telemonitor.dm
@@ -27,7 +27,7 @@
/obj/machinery/computer/telecomms/monitor/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/telecomms),
+ get_asset_datum(/datum/asset/spritesheet_batched/telecomms),
)
/obj/machinery/computer/telecomms/monitor/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/game/objects/items/airlock_painter.dm b/code/game/objects/items/airlock_painter.dm
index 660b6bd3305..52962c0684e 100644
--- a/code/game/objects/items/airlock_painter.dm
+++ b/code/game/objects/items/airlock_painter.dm
@@ -176,7 +176,7 @@
/// The full icon state of the decal being printed.
var/stored_decal_total = "warningline"
/// The type path of the spritesheet being used for the frontend.
- var/spritesheet_type = /datum/asset/spritesheet/decals // spritesheet containing previews
+ var/spritesheet_type = /datum/asset/spritesheet_batched/decals // spritesheet containing previews
/// Does this printer implementation support custom colors?
var/supports_custom_color = FALSE
/// Current custom color
@@ -266,7 +266,7 @@
/obj/item/airlock_painter/decal/ui_static_data(mob/user)
. = ..()
- var/datum/asset/spritesheet/icon_assets = get_asset_datum(spritesheet_type)
+ var/datum/asset/spritesheet_batched/icon_assets = get_asset_datum(spritesheet_type)
.["icon_prefix"] = "[icon_assets.name]32x32"
.["supports_custom_color"] = supports_custom_color
@@ -332,27 +332,16 @@
stored_custom_color = chosen_color
stored_color = chosen_color
-/datum/asset/spritesheet/decals
+/datum/asset/spritesheet_batched/decals
name = "floor_decals"
-
- /// The floor icon used for blend_preview_floor()
+ ignore_dir_errors = TRUE
+ /// The floor icon used for previews
var/preview_floor_icon = 'icons/turf/floors.dmi'
- /// The floor icon state used for blend_preview_floor()
+ /// The floor icon state used for previews
var/preview_floor_state = "floor"
/// The associated decal painter type to grab decals, colors, etc from.
var/obj/item/airlock_painter/decal/painter_type = /obj/item/airlock_painter/decal
-/**
- * Underlay an example floor for preview purposes, and return the new icon.
- *
- * Arguments:
- * * decal - the decal to place over the example floor tile
- */
-/datum/asset/spritesheet/decals/proc/blend_preview_floor(icon/decal)
- var/icon/final = icon(preview_floor_icon, preview_floor_state)
- final.Blend(decal, ICON_OVERLAY)
- return final
-
/**
* Insert a specific state into the spritesheet.
*
@@ -361,14 +350,15 @@
* * dir - the given direction.
* * color - the given color.
*/
-/datum/asset/spritesheet/decals/proc/insert_state(decal, dir, color)
+/datum/asset/spritesheet_batched/decals/proc/insert_state(decal, dir, color)
// Special case due to icon_state names
var/icon_state_color = color == "yellow" ? "" : color
- var/icon/final = blend_preview_floor(icon('icons/turf/decals.dmi', "[decal][icon_state_color ? "_" : ""][icon_state_color]", dir))
- Insert("[decal]_[dir]_[color]", final)
+ var/datum/universal_icon/floor = uni_icon(preview_floor_icon, preview_floor_state)
+ floor.blend_icon(uni_icon('icons/turf/decals.dmi', "[decal][icon_state_color ? "_" : ""][icon_state_color]", dir), ICON_OVERLAY)
+ insert_icon("[decal]_[dir]_[color]", floor)
-/datum/asset/spritesheet/decals/create_spritesheets()
+/datum/asset/spritesheet_batched/decals/create_spritesheets()
// Must actually create because initial(type) doesn't work for /lists for some reason.
var/obj/item/airlock_painter/decal/painter = new painter_type()
@@ -394,7 +384,7 @@
stored_dir = 2
stored_color = "#D4D4D432"
stored_decal = "tile_corner"
- spritesheet_type = /datum/asset/spritesheet/decals/tiles
+ spritesheet_type = /datum/asset/spritesheet_batched/decals/tiles
supports_custom_color = TRUE
// Colors can have a an alpha component as RGBA, or just be RGB and use default alpha
color_list = list(
@@ -448,11 +438,12 @@
target.AddElement(/datum/element/decal, 'icons/turf/decals.dmi', source_decal, source_dir, null, null, decal_alpha, decal_color, null, FALSE, null)
-/datum/asset/spritesheet/decals/tiles
+/datum/asset/spritesheet_batched/decals/tiles
name = "floor_tile_decals"
+ ignore_dir_errors = TRUE
painter_type = /obj/item/airlock_painter/decal/tile
-/datum/asset/spritesheet/decals/tiles/insert_state(decal, dir, color)
+/datum/asset/spritesheet_batched/decals/tiles/insert_state(decal, dir, color)
// Account for 8-sided decals.
var/source_decal = decal
var/source_dir = dir
@@ -468,13 +459,14 @@
render_color = tile_type.rgba_regex.group[1]
render_alpha = text2num(tile_type.rgba_regex.group[2], 16)
- var/icon/colored_icon = icon('icons/turf/decals.dmi', source_decal, dir=source_dir)
- colored_icon.ChangeOpacity(render_alpha * 0.008)
+ var/datum/universal_icon/colored_icon = uni_icon('icons/turf/decals.dmi', source_decal, dir=source_dir)
+ colored_icon.change_opacity(render_alpha / 255)
if(color == "custom")
// Do a fun rainbow pattern to stand out while still being static.
- colored_icon.Blend(icon('icons/effects/random_spawners.dmi', "rainbow"), ICON_MULTIPLY)
+ colored_icon.blend_icon(uni_icon('icons/effects/random_spawners.dmi', "rainbow"), ICON_MULTIPLY)
else
- colored_icon.Blend(render_color, ICON_MULTIPLY)
+ colored_icon.blend_color(render_color, ICON_MULTIPLY)
- colored_icon = blend_preview_floor(colored_icon)
- Insert("[decal]_[dir]_[replacetext(color, "#", "")]", colored_icon)
+ var/datum/universal_icon/floor = uni_icon(preview_floor_icon, preview_floor_state)
+ floor.blend_icon(colored_icon, ICON_OVERLAY)
+ insert_icon("[decal]_[dir]_[replacetext(color, "#", "")]", floor)
diff --git a/code/game/objects/items/rcd/RCD.dm b/code/game/objects/items/rcd/RCD.dm
index 370a6ea5f62..1bc5df2ed86 100644
--- a/code/game/objects/items/rcd/RCD.dm
+++ b/code/game/objects/items/rcd/RCD.dm
@@ -314,7 +314,7 @@
/obj/item/construction/rcd/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/rcd),
+ get_asset_datum(/datum/asset/spritesheet_batched/rcd),
)
/obj/item/construction/rcd/ui_host(mob/user)
diff --git a/code/game/objects/items/rcd/RPD.dm b/code/game/objects/items/rcd/RPD.dm
index 933f8c1c648..d78a56d173c 100644
--- a/code/game/objects/items/rcd/RPD.dm
+++ b/code/game/objects/items/rcd/RPD.dm
@@ -148,7 +148,7 @@
/obj/item/pipe_dispenser/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/pipes),
+ get_asset_datum(/datum/asset/spritesheet_batched/pipes),
)
/obj/item/pipe_dispenser/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/game/objects/items/rcd/RPLD.dm b/code/game/objects/items/rcd/RPLD.dm
index 7f733811122..c85c7b6bb28 100644
--- a/code/game/objects/items/rcd/RPLD.dm
+++ b/code/game/objects/items/rcd/RPLD.dm
@@ -113,7 +113,7 @@
/obj/item/construction/plumbing/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/plumbing),
+ get_asset_datum(/datum/asset/spritesheet_batched/plumbing),
)
/obj/item/construction/plumbing/ui_static_data(mob/user)
diff --git a/code/game/objects/items/rcd/RTD.dm b/code/game/objects/items/rcd/RTD.dm
index 7a6802d54b4..b3c5f359cb9 100644
--- a/code/game/objects/items/rcd/RTD.dm
+++ b/code/game/objects/items/rcd/RTD.dm
@@ -144,7 +144,7 @@
/obj/item/construction/rtd/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/rtd),
+ get_asset_datum(/datum/asset/spritesheet_batched/rtd),
)
/obj/item/construction/rtd/attack_self(mob/user)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 802c405ba23..a2ea441d823 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -680,6 +680,33 @@ ADMIN_VERB(give_ai_controller, R_FUN, "Give AI Controller", ADMIN_VERB_NO_DESCRI
var/datum/admin_ai_template/using_template = new chosen_type
using_template.apply(my_guy, user)
+ADMIN_VERB(clear_legacy_asset_cache, R_DEBUG, "Clear Legacy Asset Cache", "Clears the legacy asset cache, regenerating it immediately (may cause lag).", ADMIN_CATEGORY_DEBUG)
+ if(!CONFIG_GET(flag/cache_assets))
+ to_chat(user, span_warning("Asset caching is disabled in the config!"))
+ return
+ var/regenerated = 0
+ for(var/datum/asset/target_spritesheet as anything in subtypesof(/datum/asset))
+ if(!initial(target_spritesheet.cross_round_cachable))
+ continue
+ if(target_spritesheet == initial(target_spritesheet._abstract))
+ continue
+ var/datum/asset/asset_datum = GLOB.asset_datums[target_spritesheet]
+ asset_datum.regenerate()
+ regenerated++
+ to_chat(user, span_notice("Regenerated [regenerated] asset\s."))
+
+ADMIN_VERB(clear_smart_asset_cache, R_DEBUG, "Clear Smart Asset Cache", "Clear the smart asset cache, causing it to regenerate next round.", ADMIN_CATEGORY_DEBUG)
+ if(!CONFIG_GET(flag/smart_cache_assets))
+ to_chat(user, span_warning("Smart asset caching is disabled in the config!"))
+ return
+ var/cleared = 0
+ for(var/datum/asset/spritesheet_batched/target_spritesheet as anything in subtypesof(/datum/asset/spritesheet_batched))
+ if(target_spritesheet == initial(target_spritesheet._abstract))
+ continue
+ fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.[initial(target_spritesheet.name)].json")
+ cleared++
+ to_chat(user, span_notice("Cleared [cleared] asset\s."))
+
ADMIN_VERB(give_ai_speech, R_FUN, "Give Random AI Speech", ADMIN_VERB_NO_DESCRIPTION, ADMIN_CATEGORY_HIDDEN, mob/living/my_guy)
if (isnull(my_guy.ai_controller))
var/create_controller = tgui_alert(user, "Target has no AI controller, add one?", "Give AI?", list("Yes", "No")) == "Yes"
diff --git a/code/modules/admin/verbs/light_debug.dm b/code/modules/admin/verbs/light_debug.dm
index 0611575e209..892af724959 100644
--- a/code/modules/admin/verbs/light_debug.dm
+++ b/code/modules/admin/verbs/light_debug.dm
@@ -198,7 +198,7 @@ GLOBAL_LIST_EMPTY(light_debugged_atoms)
ui.open()
/atom/movable/screen/light_button/edit/ui_assets(mob/user)
- return list(get_asset_datum(/datum/asset/spritesheet/lights))
+ return list(get_asset_datum(/datum/asset/spritesheet_batched/lights))
/atom/movable/screen/light_button/edit/ui_data()
var/list/data = list()
@@ -371,7 +371,7 @@ GLOBAL_LIST_EMPTY(light_debugged_atoms)
ui.open()
/datum/action/spawn_light/ui_assets(mob/user)
- return list(get_asset_datum(/datum/asset/spritesheet/lights))
+ return list(get_asset_datum(/datum/asset/spritesheet_batched/lights))
/datum/action/spawn_light/ui_data()
var/list/data = list()
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
index 48e5e7e39a4..033ec58a4cd 100644
--- a/code/modules/asset_cache/asset_list.dm
+++ b/code/modules/asset_cache/asset_list.dm
@@ -1,5 +1,3 @@
-#define ASSET_CROSS_ROUND_CACHE_DIRECTORY "cache/assets"
-
//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
@@ -63,6 +61,17 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/proc/should_refresh()
return !cross_round_cachable || !CONFIG_GET(flag/cache_assets)
+/// Immediately regenerate the asset, overwriting any cache.
+/datum/asset/proc/regenerate()
+ unregister()
+ cached_serialized_url_mappings = null
+ cached_serialized_url_mappings_transport_type = null
+ register()
+
+/// Unregisters any assets from the transport.
+/datum/asset/proc/unregister()
+ CRASH("unregister() not implemented for asset [type]!")
+
/// Simply takes any generated file and saves it to the round-specific /logs folder. Useful for debugging potential issues with spritesheet generation/display.
/// Only called when the SAVE_SPRITESHEETS config option is uncommented.
/datum/asset/proc/save_to_logs(file_name, file_location)
@@ -103,6 +112,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
for (var/asset_name in assets)
.[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name])
+/datum/asset/simple/unregister()
+ for (var/asset_name in assets)
+ SSassets.transport.unregister_asset(asset_name)
+
// For registering or sending multiple others at once
/datum/asset/group
_abstract = /datum/asset/group
@@ -123,374 +136,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
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
- cross_round_cachable = TRUE
- var/name
- /// List of arguments to pass into queuedInsert
- /// Exists so we can queue icon insertion, mostly for stuff like preferences
- var/list/to_generate = list()
- var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped)
- var/list/sprites = list() // "foo_bar" -> list("32x32", 5)
- var/list/cached_spritesheets_needed
- var/generating_cache = FALSE
- var/fully_generated = FALSE
- /// If this asset should be fully loaded on new
- /// Defaults to false so we can process this stuff nicely
- var/load_immediately = FALSE
- VAR_PRIVATE
- // Kept in state so that the result is the same, even when the files are created, for this run
- should_refresh = null
-
-/datum/asset/spritesheet/proc/should_load_immediately()
-#ifdef DO_NOT_DEFER_ASSETS
- return TRUE
-#else
- return load_immediately
-#endif
-
-
-/datum/asset/spritesheet/should_refresh()
- if (..())
- return TRUE
-
- if (isnull(should_refresh))
- // `fexists` seems to always fail on static-time
- should_refresh = !fexists(css_cache_filename()) || !fexists(data_cache_filename())
-
- return should_refresh
-
-/datum/asset/spritesheet/register()
- SHOULD_NOT_OVERRIDE(TRUE)
-
- if (!name)
- CRASH("spritesheet [type] cannot register without a name")
-
- if (!should_refresh() && read_from_cache())
- fully_generated = TRUE
- return
-
- // If it's cached, may as well load it now, while the loading is cheap
- if(CONFIG_GET(flag/cache_assets) && cross_round_cachable)
- load_immediately = TRUE
-
- create_spritesheets()
- if(should_load_immediately())
- realize_spritesheets(yield = FALSE)
- else
- SSasset_loading.queue_asset(src)
-
-/datum/asset/spritesheet/proc/realize_spritesheets(yield)
- if(fully_generated)
- return
- while(length(to_generate))
- var/list/stored_args = to_generate[to_generate.len]
- to_generate.len--
- queuedInsert(arglist(stored_args))
- if(yield && TICK_CHECK)
- return
-
- ensure_stripped()
- for(var/size_id in sizes)
- var/size = sizes[size_id]
- SSassets.transport.register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
- var/css_name = "spritesheet_[name].css"
- var/file_directory = "data/spritesheets/[css_name]"
- fdel(file_directory)
- text2file(generate_css(), file_directory)
- SSassets.transport.register_asset(css_name, fcopy_rsc(file_directory))
-
- if(CONFIG_GET(flag/save_spritesheets))
- save_to_logs(file_name = css_name, file_location = file_directory)
-
- fdel(file_directory)
-
- if (CONFIG_GET(flag/cache_assets) && cross_round_cachable)
- write_to_cache()
- fully_generated = TRUE
- // If we were ever in there, remove ourselves
- SSasset_loading.dequeue_asset(src)
-
-/datum/asset/spritesheet/queued_generation()
- realize_spritesheets(yield = TRUE)
-
-/datum/asset/spritesheet/ensure_ready()
- if(!fully_generated)
- realize_spritesheets(yield = FALSE)
- return ..()
-
-/datum/asset/spritesheet/send(client/client)
- if (!name)
- return
-
- if (!should_refresh())
- return send_from_cache(client)
-
- var/all = list("spritesheet_[name].css")
- for(var/size_id in sizes)
- all += "[name]_[size_id].png"
- . = SSassets.transport.send_assets(client, all)
-
-/datum/asset/spritesheet/get_url_mappings()
- if (!name)
- return
-
- if (!should_refresh())
- return get_cached_url_mappings()
-
- . = list("spritesheet_[name].css" = SSassets.transport.get_asset_url("spritesheet_[name].css"))
- for(var/size_id in sizes)
- .["[name]_[size_id].png"] = SSassets.transport.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
-
- // save flattened version
- var/png_name = "[name]_[size_id].png"
- var/file_directory = "data/spritesheets/[png_name]"
- fcopy(size[SPRSZ_ICON], file_directory)
- var/error = rustg_dmi_strip_metadata(file_directory)
- if(length(error))
- stack_trace("Failed to strip [png_name]: [error]")
- size[SPRSZ_STRIPPED] = icon(file_directory)
-
- // this is useful here for determining if weird sprite issues (like having a white background) are a cause of what we're doing DM-side or not since we can see the full flattened thing at-a-glance.
- if(CONFIG_GET(flag/save_spritesheets))
- save_to_logs(file_name = png_name, file_location = file_directory)
-
- fdel(file_directory)
-
-/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_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/css_cache_filename()
- return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css"
-
-/datum/asset/spritesheet/proc/data_cache_filename()
- return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].json"
-
-/datum/asset/spritesheet/proc/read_from_cache()
- return read_css_from_cache() && read_data_from_cache()
-
-/datum/asset/spritesheet/proc/read_css_from_cache()
- var/replaced_css = file2text(css_cache_filename())
-
- var/regex/find_background_urls = regex(@"background:url\('%(.+?)%'\)", "g")
- while (find_background_urls.Find(replaced_css))
- var/asset_id = find_background_urls.group[1]
- var/asset_cache_item = SSassets.transport.register_asset(asset_id, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[asset_id]")
- var/asset_url = SSassets.transport.get_asset_url(asset_cache_item = asset_cache_item)
- replaced_css = replacetext(replaced_css, find_background_urls.match, "background:url('[asset_url]')")
- LAZYADD(cached_spritesheets_needed, asset_id)
-
- var/finalized_name = "spritesheet_[name].css"
- var/replaced_css_filename = "data/spritesheets/[finalized_name]"
- rustg_file_write(replaced_css, replaced_css_filename)
- SSassets.transport.register_asset(finalized_name, replaced_css_filename)
-
- if(CONFIG_GET(flag/save_spritesheets))
- save_to_logs(file_name = finalized_name, file_location = replaced_css_filename)
-
- fdel(replaced_css_filename)
-
- return TRUE
-
-/datum/asset/spritesheet/proc/read_data_from_cache()
- var/json = json_decode(file2text(data_cache_filename()))
-
- if (islist(json["sprites"]))
- sprites = json["sprites"]
-
- return TRUE
-
-/datum/asset/spritesheet/proc/send_from_cache(client/client)
- if (isnull(cached_spritesheets_needed))
- stack_trace("cached_spritesheets_needed was null when sending assets from [type] from cache")
- cached_spritesheets_needed = list()
-
- return SSassets.transport.send_assets(client, cached_spritesheets_needed + "spritesheet_[name].css")
-
-/// Returns the URL to put in the background:url of the CSS asset
-/datum/asset/spritesheet/proc/get_background_url(asset)
- if (generating_cache)
- return "%[asset]%"
- else
- return SSassets.transport.get_asset_url(asset)
-
-/datum/asset/spritesheet/proc/write_to_cache()
- write_css_to_cache()
- write_data_to_cache()
-
-/datum/asset/spritesheet/proc/write_css_to_cache()
- for (var/size_id in sizes)
- fcopy(SSassets.cache["[name]_[size_id].png"].resource, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name]_[size_id].png")
-
- generating_cache = TRUE
- var/mock_css = generate_css()
- generating_cache = FALSE
-
- rustg_file_write(mock_css, css_cache_filename())
-
-/datum/asset/spritesheet/proc/write_data_to_cache()
- rustg_file_write(json_encode(list(
- "sprites" = sprites,
- )), data_cache_filename())
-
-/datum/asset/spritesheet/proc/get_cached_url_mappings()
- var/list/mappings = list()
- mappings["spritesheet_[name].css"] = SSassets.transport.get_asset_url("spritesheet_[name].css")
-
- for (var/asset_name in cached_spritesheets_needed)
- mappings[asset_name] = SSassets.transport.get_asset_url(asset_name)
-
- return mappings
-
-/// Override this in order to start the creation of the spritehseet.
-/// This is where all your Insert, InsertAll, etc calls should be inside.
-/datum/asset/spritesheet/proc/create_spritesheets()
- SHOULD_CALL_PARENT(FALSE)
- CRASH("create_spritesheets() not implemented for [type]!")
-
-/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
- if(should_load_immediately())
- queuedInsert(sprite_name, I, icon_state, dir, frame, moving)
- else
- to_generate += list(args.Copy())
-
-/datum/asset/spritesheet/proc/queuedInsert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
-#ifdef UNIT_TESTS
- if (I && icon_state && !icon_exists(I, icon_state)) // check the base icon prior to extracting the state we want
- stack_trace("Tried to insert nonexistent icon_state '[icon_state]' from [I] into spritesheet [name] ([type])")
- return
-#endif
- 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/start_usage = world.tick_usage
-
- //any sprite modifications we want to do (aka, coloring a greyscaled asset)
- I = ModifyInserted(I)
- 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]++
- // Icons are essentially representations of files + modifications
- // Because of this, byond keeps them in a cache. It does this in a really dumb way tho
- // It's essentially a FIFO queue. So after we do icon() some amount of times, our old icons go out of cache
- // When this happens it becomes impossible to modify them, trying to do so will instead throw a
- // "bad icon" error.
- // What we're doing here is ensuring our icon is in the cache by refreshing it, so we can modify it w/o runtimes.
- var/icon/sheet = size[SPRSZ_ICON]
- var/icon/sheet_copy = icon(sheet)
- size[SPRSZ_STRIPPED] = null
- sheet_copy.Insert(I, icon_state=sprite_name)
- size[SPRSZ_ICON] = sheet_copy
-
- sprites[sprite_name] = list(size_id, position)
- else
- sizes[size_id] = size = list(1, I, null)
- sprites[sprite_name] = list(size_id, 0)
-
- SSblackbox.record_feedback("tally", "spritesheet_queued_insert_time", TICK_USAGE_TO_MS(start_usage), name)
-
-/**
- * A simple proc handing the Icon for you to modify before it gets turned into an asset.
- *
- * Arguments:
- * * I: icon being turned into an asset
- */
-/datum/asset/spritesheet/proc/ModifyInserted(icon/pre_asset)
- return pre_asset
-
-/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/css_filename()
- return SSassets.transport.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 {""}
-
-/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]"}
-
-/**
- * Returns the size class (ex design32x32) for a given sprite's icon
- *
- * Arguments:
- * * sprite_name - The sprite to get the size of
- */
-/datum/asset/spritesheet/proc/icon_size_id(sprite_name)
- var/sprite = sprites[sprite_name]
- if (!sprite)
- return null
- var/size_id = sprite[SPR_SIZE]
- return "[name][size_id]"
-
-#undef SPR_SIZE
-#undef SPR_IDX
-#undef SPRSZ_COUNT
-#undef SPRSZ_ICON
-#undef SPRSZ_STRIPPED
-
+/datum/asset/group/unregister()
+ for (var/type in children)
+ var/datum/asset/A = get_asset_datum(type)
+ A.unregister()
/datum/asset/changelog_item
_abstract = /datum/asset/changelog_item
@@ -510,13 +159,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
return
. = list("[item_filename]" = SSassets.transport.get_asset_url(item_filename))
-/datum/asset/spritesheet/simple
- _abstract = /datum/asset/spritesheet/simple
- var/list/assets
-
-/datum/asset/spritesheet/simple/create_spritesheets()
- for (var/key in assets)
- Insert(key, assets[key])
+/datum/asset/changelog_item/unregister()
+ if (!item_filename)
+ return
+ SSassets.transport.unregister_asset(item_filename)
//Generates assets based on iconstates of a single icon
/datum/asset/simple/icon_states
@@ -617,13 +263,13 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/json/register()
var/filename = "data/[name].json"
fdel(filename)
- text2file(json_encode(generate()), filename)
+ rustg_file_write(json_encode(generate()), filename)
SSassets.transport.register_asset("[name].json", fcopy_rsc(filename))
fdel(filename)
/// Returns the data that will be JSON encoded
/datum/asset/json/proc/generate()
- SHOULD_CALL_PARENT(FALSE)
CRASH("generate() not implemented for [type]!")
-#undef ASSET_CROSS_ROUND_CACHE_DIRECTORY
+/datum/asset/json/unregister()
+ SSassets.transport.unregister_asset("[name].json")
diff --git a/code/modules/asset_cache/assets/achievements.dm b/code/modules/asset_cache/assets/achievements.dm
index 91f2d75b6d5..2246a138359 100644
--- a/code/modules/asset_cache/assets/achievements.dm
+++ b/code/modules/asset_cache/assets/achievements.dm
@@ -1,11 +1,13 @@
-/datum/asset/spritesheet/simple/achievements
+/datum/asset/spritesheet_batched/achievements
name = "achievements"
-/datum/asset/spritesheet/simple/achievements/create_spritesheets()
- InsertAll("achievement", ACHIEVEMENTS_SET)
+/datum/asset/spritesheet_batched/achievements/create_spritesheets()
+ for(var/icon_state_name in icon_states(ACHIEVEMENTS_SET))
+ insert_icon("achievement-[icon_state_name]", uni_icon(ACHIEVEMENTS_SET, icon_state_name))
// catch achievements which are pulling icons from another file
for(var/datum/award/other_award as anything in subtypesof(/datum/award))
var/icon = initial(other_award.icon)
- if (icon != ACHIEVEMENTS_SET)
- var/icon_state = initial(other_award.icon_state)
- Insert("achievement-[icon_state]", icon, icon_state=icon_state)
+ if (icon == ACHIEVEMENTS_SET)
+ continue
+ var/icon_state_name = initial(other_award.icon_state)
+ insert_icon("achievement-[icon_state_name]", uni_icon(icon, icon_state_name))
diff --git a/code/modules/asset_cache/assets/bibles.dm b/code/modules/asset_cache/assets/bibles.dm
index 948af216063..61d724de2e0 100644
--- a/code/modules/asset_cache/assets/bibles.dm
+++ b/code/modules/asset_cache/assets/bibles.dm
@@ -1,10 +1,11 @@
-/datum/asset/spritesheet/bibles
+/datum/asset/spritesheet_batched/bibles
name = "bibles"
-/datum/asset/spritesheet/bibles/create_spritesheets()
+/datum/asset/spritesheet_batched/bibles/create_spritesheets()
var/obj/item/book/bible/holy_template = /obj/item/book/bible
- InsertAll("display", initial(holy_template.icon))
+ var/target_icon = initial(holy_template.icon)
+ var/datum/icon_transformer/transform = new()
+ transform.scale(224, 224) // Scale up by 7x
-/datum/asset/spritesheet/bibles/ModifyInserted(icon/pre_asset)
- pre_asset.Scale(224, 224) // Scale up by 7x
- return pre_asset
+ for (var/icon_state_name in icon_states(target_icon))
+ insert_icon("display-[icon_state_name]", uni_icon(target_icon, icon_state_name, transform=transform))
diff --git a/code/modules/asset_cache/assets/chat.dm b/code/modules/asset_cache/assets/chat.dm
index 1da0869a85a..471e8ce85dc 100644
--- a/code/modules/asset_cache/assets/chat.dm
+++ b/code/modules/asset_cache/assets/chat.dm
@@ -1,14 +1,13 @@
-/datum/asset/spritesheet/chat
+/datum/asset/spritesheet_batched/chat
name = "chat"
-/datum/asset/spritesheet/chat/create_spritesheets()
- InsertAll("emoji", EMOJI_SET)
+/datum/asset/spritesheet_batched/chat/create_spritesheets()
+ insert_all_icons("emoji", EMOJI_SET)
// pre-loading all lanugage icons also helps to avoid meta
- InsertAll("language", 'icons/ui/chat/language.dmi')
+ insert_all_icons("language", 'icons/ui/chat/language.dmi')
// catch languages which are pulling icons from another file
- for(var/path in typesof(/datum/language))
- var/datum/language/L = path
+ for(var/datum/language/L as anything in subtypesof(/datum/language))
var/icon = initial(L.icon)
if (icon != 'icons/ui/chat/language.dmi')
var/icon_state = initial(L.icon_state)
- Insert("language-[icon_state]", icon, icon_state=icon_state)
+ insert_icon("language-[icon_state]", uni_icon(icon, icon_state))
diff --git a/code/modules/asset_cache/assets/chemmaster.dm b/code/modules/asset_cache/assets/chemmaster.dm
index 991d999b2cd..ffc45cf4b36 100644
--- a/code/modules/asset_cache/assets/chemmaster.dm
+++ b/code/modules/asset_cache/assets/chemmaster.dm
@@ -1,8 +1,8 @@
///Icons for containers printed in ChemMaster
-/datum/asset/spritesheet/chemmaster
+/datum/asset/spritesheet_batched/chemmaster
name = "chemmaster"
-/datum/asset/spritesheet/chemmaster/create_spritesheets()
+/datum/asset/spritesheet_batched/chemmaster/create_spritesheets()
var/list/ids = list()
for(var/category in GLOB.reagent_containers)
for(var/obj/item/reagent_containers/container as anything in GLOB.reagent_containers[category])
@@ -12,4 +12,4 @@
if(id in ids) // exclude duplicate containers
continue
ids += id
- Insert(id, icon_file, icon_state)
+ insert_icon(id, uni_icon(icon_file, icon_state))
diff --git a/code/modules/asset_cache/assets/condiments.dm b/code/modules/asset_cache/assets/condiments.dm
deleted file mode 100644
index 8b471487324..00000000000
--- a/code/modules/asset_cache/assets/condiments.dm
+++ /dev/null
@@ -1,24 +0,0 @@
-/datum/asset/spritesheet/simple/condiments
- name = "condiments"
- assets = list(
- CONDIMASTER_STYLE_FALLBACK = 'icons/ui/condiments/bottle.png',
- "flour" = 'icons/ui/condiments/flour.png',
- "rice" = 'icons/ui/condiments/rice.png',
- "sugar" = 'icons/ui/condiments/sugar.png',
- "milk" = 'icons/ui/condiments/milk.png',
- "enzyme" = 'icons/ui/condiments/enzyme.png',
- "capsaicin" = 'icons/ui/condiments/hotsauce.png',
- "frostoil" = 'icons/ui/condiments/coldsauce.png',
- "bbqsauce" = 'icons/ui/condiments/bbqsauce.png',
- "soymilk" = 'icons/ui/condiments/soymilk.png',
- "soysauce" = 'icons/ui/condiments/soysauce.png',
- "ketchup" = 'icons/ui/condiments/ketchup.png',
- "mayonnaise" = 'icons/ui/condiments/mayonnaise.png',
- "oliveoil" = 'icons/ui/condiments/oliveoil.png',
- "cooking_oil" = 'icons/ui/condiments/cookingoil.png',
- "peanut_butter" = 'icons/ui/condiments/peanutbutter.png',
- "cherryjelly" = 'icons/ui/condiments/cherryjelly.png',
- "honey" = 'icons/ui/condiments/honey.png',
- "blackpepper" = 'icons/ui/condiments/peppermillsmall.png',
- "sodiumchloride" = 'icons/ui/condiments/saltshakersmall.png',
- )
diff --git a/code/modules/asset_cache/assets/crafting.dm b/code/modules/asset_cache/assets/crafting.dm
index 1d6b9469d1f..a196736fcfe 100644
--- a/code/modules/asset_cache/assets/crafting.dm
+++ b/code/modules/asset_cache/assets/crafting.dm
@@ -1,17 +1,17 @@
///Representative icons for the contents of each crafting recipe
-/datum/asset/spritesheet/crafting
+/datum/asset/spritesheet_batched/crafting
name = "crafting"
-/datum/asset/spritesheet/crafting/create_spritesheets()
+/datum/asset/spritesheet_batched/crafting/create_spritesheets()
var/id = 1
for(var/atom in GLOB.crafting_recipes_atoms)
add_atom_icon(atom, id++)
add_tool_icons()
-/datum/asset/spritesheet/crafting/cooking
+/datum/asset/spritesheet_batched/crafting/cooking
name = "cooking"
-/datum/asset/spritesheet/crafting/cooking/create_spritesheets()
+/datum/asset/spritesheet_batched/crafting/cooking/create_spritesheets()
var/id = 1
for(var/atom in GLOB.cooking_recipes_atoms)
add_atom_icon(atom, id++)
@@ -24,7 +24,7 @@
* If it a reagent, it will use the default container's icon state,
* OR if it has a glass style associated, it will use that
*/
-/datum/asset/spritesheet/crafting/proc/add_atom_icon(ingredient_typepath, id)
+/datum/asset/spritesheet_batched/crafting/proc/add_atom_icon(ingredient_typepath, id)
var/icon_file
var/icon_state
var/obj/preview_item = ingredient_typepath
@@ -43,32 +43,32 @@
if(!icon_exists_or_scream(icon_file, icon_state))
return
- Insert("a[id]", icon(icon_file, icon_state, SOUTH))
+ insert_icon("a[id]", uni_icon(icon_file, icon_state, SOUTH))
///Adds tool icons to the spritesheet
-/datum/asset/spritesheet/crafting/proc/add_tool_icons()
+/datum/asset/spritesheet_batched/crafting/proc/add_tool_icons()
var/list/tool_icons = list(
- TOOL_CROWBAR = icon('icons/obj/tools.dmi', "crowbar"),
- TOOL_MULTITOOL = icon('icons/obj/devices/tool.dmi', "multitool"),
- TOOL_SCREWDRIVER = icon('icons/obj/tools.dmi', "screwdriver_map"),
- TOOL_WIRECUTTER = icon('icons/obj/tools.dmi', "cutters_map"),
- TOOL_WRENCH = icon('icons/obj/tools.dmi', "wrench"),
- TOOL_WELDER = icon('icons/obj/tools.dmi', "welder"),
- TOOL_ANALYZER = icon('icons/obj/devices/scanner.dmi', "analyzer"),
- TOOL_MINING = icon('icons/obj/mining.dmi', "minipick"),
- TOOL_SHOVEL = icon('icons/obj/mining.dmi', "spade"),
- TOOL_RETRACTOR = icon('icons/obj/medical/surgery_tools.dmi', "retractor"),
- TOOL_HEMOSTAT = icon('icons/obj/medical/surgery_tools.dmi', "hemostat"),
- TOOL_CAUTERY = icon('icons/obj/medical/surgery_tools.dmi', "cautery"),
- TOOL_DRILL = icon('icons/obj/medical/surgery_tools.dmi', "drill"),
- TOOL_SCALPEL = icon('icons/obj/medical/surgery_tools.dmi', "scalpel"),
- TOOL_SAW = icon('icons/obj/medical/surgery_tools.dmi', "saw"),
- TOOL_BONESET = icon('icons/obj/medical/surgery_tools.dmi', "bonesetter"),
- TOOL_KNIFE = icon('icons/obj/service/kitchen.dmi', "knife"),
- TOOL_BLOODFILTER = icon('icons/obj/medical/surgery_tools.dmi', "bloodfilter"),
- TOOL_ROLLINGPIN = icon('icons/obj/service/kitchen.dmi', "rolling_pin"),
- TOOL_RUSTSCRAPER = icon('icons/obj/tools.dmi', "wirebrush"),
+ TOOL_CROWBAR = uni_icon('icons/obj/tools.dmi', "crowbar"),
+ TOOL_MULTITOOL = uni_icon('icons/obj/devices/tool.dmi', "multitool"),
+ TOOL_SCREWDRIVER = uni_icon('icons/obj/tools.dmi', "screwdriver_map"),
+ TOOL_WIRECUTTER = uni_icon('icons/obj/tools.dmi', "cutters_map"),
+ TOOL_WRENCH = uni_icon('icons/obj/tools.dmi', "wrench"),
+ TOOL_WELDER = uni_icon('icons/obj/tools.dmi', "welder"),
+ TOOL_ANALYZER = uni_icon('icons/obj/devices/scanner.dmi', "analyzer"),
+ TOOL_MINING = uni_icon('icons/obj/mining.dmi', "minipick"),
+ TOOL_SHOVEL = uni_icon('icons/obj/mining.dmi', "spade"),
+ TOOL_RETRACTOR = uni_icon('icons/obj/medical/surgery_tools.dmi', "retractor"),
+ TOOL_HEMOSTAT = uni_icon('icons/obj/medical/surgery_tools.dmi', "hemostat"),
+ TOOL_CAUTERY = uni_icon('icons/obj/medical/surgery_tools.dmi', "cautery"),
+ TOOL_DRILL = uni_icon('icons/obj/medical/surgery_tools.dmi', "drill"),
+ TOOL_SCALPEL = uni_icon('icons/obj/medical/surgery_tools.dmi', "scalpel"),
+ TOOL_SAW = uni_icon('icons/obj/medical/surgery_tools.dmi', "saw"),
+ TOOL_BONESET = uni_icon('icons/obj/medical/surgery_tools.dmi', "bonesetter"),
+ TOOL_KNIFE = uni_icon('icons/obj/service/kitchen.dmi', "knife"),
+ TOOL_BLOODFILTER = uni_icon('icons/obj/medical/surgery_tools.dmi', "bloodfilter"),
+ TOOL_ROLLINGPIN = uni_icon('icons/obj/service/kitchen.dmi', "rolling_pin"),
+ TOOL_RUSTSCRAPER = uni_icon('icons/obj/tools.dmi', "wirebrush"),
)
for(var/tool in tool_icons)
- Insert(replacetext(tool, " ", ""), tool_icons[tool])
+ insert_icon(replacetext(tool, " ", ""), tool_icons[tool])
diff --git a/code/modules/asset_cache/assets/emojipedia.dm b/code/modules/asset_cache/assets/emojipedia.dm
index 200e3770ff4..9dd40dd56a4 100644
--- a/code/modules/asset_cache/assets/emojipedia.dm
+++ b/code/modules/asset_cache/assets/emojipedia.dm
@@ -1,5 +1,5 @@
-/datum/asset/spritesheet/emojipedia
+/datum/asset/spritesheet_batched/emojipedia
name = "emojipedia"
-/datum/asset/spritesheet/emojipedia/create_spritesheets()
- InsertAll("", EMOJI_SET)
+/datum/asset/spritesheet_batched/emojipedia/create_spritesheets()
+ insert_all_icons("", EMOJI_SET)
diff --git a/code/modules/asset_cache/assets/fish.dm b/code/modules/asset_cache/assets/fish.dm
index 2fcf2b803e3..1399f8e35a3 100644
--- a/code/modules/asset_cache/assets/fish.dm
+++ b/code/modules/asset_cache/assets/fish.dm
@@ -1,14 +1,13 @@
-/datum/asset/spritesheet/fish
+/datum/asset/spritesheet_batched/fish
name = "fish"
-/datum/asset/spritesheet/fish/create_spritesheets()
+/datum/asset/spritesheet_batched/fish/create_spritesheets()
var/list/id_list = list()
- for (var/path in subtypesof(/obj/item/fish))
- var/obj/item/fish/fish_type = path
+ for (var/obj/item/fish/fish_type as anything in subtypesof(/obj/item/fish))
var/fish_icon = initial(fish_type.icon)
var/fish_icon_state = initial(fish_type.icon_state)
var/id = sanitize_css_class_name("[fish_icon][fish_icon_state]")
if(id in id_list) //no dupes
continue
id_list += id
- Insert(id, fish_icon, fish_icon_state)
+ insert_icon(id, uni_icon(fish_icon, fish_icon_state))
diff --git a/code/modules/asset_cache/assets/light_templates.dm b/code/modules/asset_cache/assets/light_templates.dm
index 1d51a21e8d6..32d9fd1c755 100644
--- a/code/modules/asset_cache/assets/light_templates.dm
+++ b/code/modules/asset_cache/assets/light_templates.dm
@@ -1,11 +1,7 @@
-/datum/asset/spritesheet/lights
+/datum/asset/spritesheet_batched/lights
name = "lights"
-/datum/asset/spritesheet/lights/create_spritesheets()
- // These two are required to ensure this spritesheet is not rendered with a fully white background
- // No I have absolutely no idea why, something something alpha maybe? but it does work, so that's for LATER!!
- Insert("light_dummy_start_fuckyoubyond", 'icons/obj/medical/bloodpack.dmi', "generic_bloodpack")
+/datum/asset/spritesheet_batched/lights/create_spritesheets()
for(var/id in GLOB.light_types)
var/datum/light_template/template = GLOB.light_types[id]
- Insert("light_fantastic_[template.id]", template.icon, template.icon_state)
- Insert("light_dummy_end_fuckyoubyond", 'icons/mob/silicon/ai.dmi', "questionmark")
+ insert_icon("light_fantastic_[template.id]", uni_icon(template.icon, template.icon_state))
diff --git a/code/modules/asset_cache/assets/mafia.dm b/code/modules/asset_cache/assets/mafia.dm
index 0e6a36420df..645f3f15238 100644
--- a/code/modules/asset_cache/assets/mafia.dm
+++ b/code/modules/asset_cache/assets/mafia.dm
@@ -1,5 +1,5 @@
-/datum/asset/spritesheet/mafia
+/datum/asset/spritesheet_batched/mafia
name = "mafia"
-/datum/asset/spritesheet/mafia/create_spritesheets()
- InsertAll("", 'icons/obj/mafia.dmi')
+/datum/asset/spritesheet_batched/mafia/create_spritesheets()
+ insert_all_icons("", 'icons/obj/mafia.dmi')
diff --git a/code/modules/asset_cache/assets/mecha.dm b/code/modules/asset_cache/assets/mecha.dm
index 3c2403cf1c3..0617c200db7 100644
--- a/code/modules/asset_cache/assets/mecha.dm
+++ b/code/modules/asset_cache/assets/mecha.dm
@@ -1,6 +1,6 @@
-/datum/asset/spritesheet/mecha_equipment
+/datum/asset/spritesheet_batched/mecha_equipment
name = "mecha_equipment"
-/datum/asset/spritesheet/mecha_equipment/create_spritesheets()
- InsertAll("", 'icons/obj/devices/mecha_equipment.dmi')
- InsertAll("", 'icons/obj/ore.dmi')
+/datum/asset/spritesheet_batched/mecha_equipment/create_spritesheets()
+ insert_all_icons("", 'icons/obj/devices/mecha_equipment.dmi')
+ insert_all_icons("", 'icons/obj/ore.dmi')
diff --git a/code/modules/asset_cache/assets/moods.dm b/code/modules/asset_cache/assets/moods.dm
index 13e4518c554..efc5f6f7d72 100644
--- a/code/modules/asset_cache/assets/moods.dm
+++ b/code/modules/asset_cache/assets/moods.dm
@@ -1,27 +1,18 @@
-/datum/asset/spritesheet/moods
+/datum/asset/spritesheet_batched/moods
name = "moods"
- var/iconinserted = 1
-/datum/asset/spritesheet/moods/create_spritesheets()
- for(var/i in 1 to 9)
- var/target_to_insert = "mood"+"[iconinserted]"
- Insert(target_to_insert, 'icons/hud/screen_gen.dmi', target_to_insert)
- iconinserted++
-
-/datum/asset/spritesheet/moods/ModifyInserted(icon/pre_asset)
- var/blended_color
- switch(iconinserted)
- if(1)
- blended_color = "#f15d36"
- if(2 to 3)
- blended_color = "#f38943"
- if(4)
- blended_color = "#dfa65b"
- if(5)
- blended_color = "#4b96c4"
- if(6)
- blended_color = "#86d656"
- else
- blended_color = "#2eeb9a"
- pre_asset.Blend(blended_color, ICON_MULTIPLY)
- return pre_asset
+/datum/asset/spritesheet_batched/moods/create_spritesheets()
+ var/list/mood_colors = list(
+ "mood1" = "#f15d36",
+ "mood2" = "#f38943",
+ "mood3" = "#f38943",
+ "mood4" = "#dfa65b",
+ "mood5" = "#4b96c4",
+ "mood6" = "#86d656",
+ "mood7" = "#2eeb9a",
+ "mood8" = "#2eeb9a",
+ "mood9" = "#2eeb9a",
+ )
+ for(var/target_to_insert in mood_colors)
+ var/blended_color = mood_colors[target_to_insert]
+ insert_icon(target_to_insert, uni_icon('icons/hud/screen_gen.dmi', target_to_insert, color=blended_color))
diff --git a/code/modules/asset_cache/assets/pda.dm b/code/modules/asset_cache/assets/pda.dm
deleted file mode 100644
index 7fd3f2f40e4..00000000000
--- a/code/modules/asset_cache/assets/pda.dm
+++ /dev/null
@@ -1,34 +0,0 @@
-/datum/asset/spritesheet/simple/pda
- name = "pda"
- assets = list(
- "atmos" = 'icons/ui/pda/pda_atmos.png',
- "back" = 'icons/ui/pda/pda_back.png',
- "bell" = 'icons/ui/pda/pda_bell.png',
- "blank" = 'icons/ui/pda/pda_blank.png',
- "boom" = 'icons/ui/pda/pda_boom.png',
- "bucket" = 'icons/ui/pda/pda_bucket.png',
- "medbot" = 'icons/ui/pda/pda_medbot.png',
- "floorbot" = 'icons/ui/pda/pda_floorbot.png',
- "cleanbot" = 'icons/ui/pda/pda_cleanbot.png',
- "crate" = 'icons/ui/pda/pda_crate.png',
- "cuffs" = 'icons/ui/pda/pda_cuffs.png',
- "eject" = 'icons/ui/pda/pda_eject.png',
- "flashlight" = 'icons/ui/pda/pda_flashlight.png',
- "honk" = 'icons/ui/pda/pda_honk.png',
- "mail" = 'icons/ui/pda/pda_mail.png',
- "medical" = 'icons/ui/pda/pda_medical.png',
- "menu" = 'icons/ui/pda/pda_menu.png',
- "mule" = 'icons/ui/pda/pda_mule.png',
- "notes" = 'icons/ui/pda/pda_notes.png',
- "power" = 'icons/ui/pda/pda_power.png',
- "rdoor" = 'icons/ui/pda/pda_rdoor.png',
- "reagent" = 'icons/ui/pda/pda_reagent.png',
- "refresh" = 'icons/ui/pda/pda_refresh.png',
- "scanner" = 'icons/ui/pda/pda_scanner.png',
- "signaler" = 'icons/ui/pda/pda_signaler.png',
- "skills" = 'icons/ui/pda/pda_skills.png',
- "status" = 'icons/ui/pda/pda_status.png',
- "dronephone" = 'icons/ui/pda/pda_dronephone.png',
- "emoji" = 'icons/ui/pda/pda_emoji.png',
- "droneblacklist" = 'icons/ui/pda/pda_droneblacklist.png',
- )
diff --git a/code/modules/asset_cache/assets/pipes.dm b/code/modules/asset_cache/assets/pipes.dm
index d7a85fa172a..af55e699ced 100644
--- a/code/modules/asset_cache/assets/pipes.dm
+++ b/code/modules/asset_cache/assets/pipes.dm
@@ -1,6 +1,7 @@
-/datum/asset/spritesheet/pipes
+/datum/asset/spritesheet_batched/pipes
name = "pipes"
+ ignore_dir_errors = TRUE
-/datum/asset/spritesheet/pipes/create_spritesheets()
+/datum/asset/spritesheet_batched/pipes/create_spritesheets()
for (var/each in list('icons/obj/pipes_n_cables/pipe_item.dmi', 'icons/obj/pipes_n_cables/disposal.dmi', 'icons/obj/pipes_n_cables/transit_tube.dmi', 'icons/obj/pipes_n_cables/hydrochem/fluid_ducts.dmi'))
- InsertAll("", each, GLOB.alldirs)
+ insert_all_icons("", each, GLOB.alldirs)
diff --git a/code/modules/asset_cache/assets/plumbing.dm b/code/modules/asset_cache/assets/plumbing.dm
index 980a85e83b0..176ba363446 100644
--- a/code/modules/asset_cache/assets/plumbing.dm
+++ b/code/modules/asset_cache/assets/plumbing.dm
@@ -1,7 +1,7 @@
-/datum/asset/spritesheet/plumbing
+/datum/asset/spritesheet_batched/plumbing
name = "plumbing-tgui"
-/datum/asset/spritesheet/plumbing/create_spritesheets()
+/datum/asset/spritesheet_batched/plumbing/create_spritesheets()
//load only what we need from the icon files,format is icon_file_name = list of icon_states we need from this file
var/list/essentials = list(
'icons/obj/medical/iv_drip.dmi' = list("plumb"),
@@ -39,5 +39,5 @@
for(var/icon_file as anything in essentials)
for(var/icon_state as anything in essentials[icon_file])
- Insert(sprite_name = icon_state, I = icon_file, icon_state = icon_state)
+ insert_icon(icon_state, uni_icon(icon_file, icon_state))
diff --git a/code/modules/asset_cache/assets/rcd.dm b/code/modules/asset_cache/assets/rcd.dm
index 16dee49c60f..432900f9764 100644
--- a/code/modules/asset_cache/assets/rcd.dm
+++ b/code/modules/asset_cache/assets/rcd.dm
@@ -1,7 +1,7 @@
-/datum/asset/spritesheet/rcd
+/datum/asset/spritesheet_batched/rcd
name = "rcd-tgui"
-/datum/asset/spritesheet/rcd/create_spritesheets()
+/datum/asset/spritesheet_batched/rcd/create_spritesheets()
for(var/root_category in GLOB.rcd_designs)
var/list/category_designs = GLOB.rcd_designs[root_category]
@@ -12,34 +12,35 @@
var/list/designs = category_designs[category]
var/sprite_name
- var/icon/sprite_icon
+
for(var/list/design as anything in designs)
var/atom/movable/path = design[RCD_DESIGN_PATH]
if(!ispath(path))
continue
sprite_name = initial(path.name)
+ var/datum/universal_icon/sprite_icon
//icon for windows are blended with grills if required and loaded from radial menu
if(ispath(path, /obj/structure/window))
if(path == /obj/structure/window)
- sprite_icon = icon(icon = 'icons/hud/radial.dmi', icon_state = "windowsize")
+ sprite_icon = uni_icon('icons/hud/radial.dmi', "windowsize")
else if(path == /obj/structure/window/reinforced)
- sprite_icon = icon(icon = 'icons/hud/radial.dmi', icon_state = "windowtype")
+ sprite_icon = uni_icon('icons/hud/radial.dmi', "windowtype")
else if(path == /obj/structure/window/fulltile || path == /obj/structure/window/reinforced/fulltile)
- sprite_icon = icon(icon = initial(path.icon), icon_state = initial(path.icon_state))
- sprite_icon.Blend(icon(icon = 'icons/obj/structures.dmi', icon_state = "grille"), ICON_UNDERLAY)
+ sprite_icon = uni_icon(initial(path.icon), initial(path.icon_state))
+ sprite_icon.blend_icon(uni_icon('icons/obj/structures.dmi', "grille"), ICON_UNDERLAY)
//icons for solid airlocks have an added solid overlay on top of their glass icons
else if(ispath(path, /obj/machinery/door/airlock))
var/obj/machinery/door/airlock/airlock_path = path
var/airlock_icon = initial(airlock_path.icon)
- sprite_icon = icon(icon = airlock_icon, icon_state = "closed")
- if(!initial(airlock_path.glass))
- sprite_icon.Blend(icon(icon = airlock_icon, icon_state = "fill_closed"), ICON_OVERLAY)
+ sprite_icon = uni_icon(airlock_icon, "closed")
+ if(!initial(airlock_path.glass) && initial(airlock_path.can_be_glass))
+ sprite_icon.blend_icon(uni_icon(airlock_icon, "fill_closed"), ICON_OVERLAY)
//for all other icons we load the paths default icon & icon state
else
- sprite_icon = icon(icon = initial(path.icon), icon_state = initial(path.icon_state))
+ sprite_icon = uni_icon(initial(path.icon), initial(path.icon_state))
- Insert(sanitize_css_class_name(sprite_name), sprite_icon)
+ insert_icon(sanitize_css_class_name(sprite_name), sprite_icon)
diff --git a/code/modules/asset_cache/assets/research_designs.dm b/code/modules/asset_cache/assets/research_designs.dm
index 6f9c96e5496..dffef470344 100644
--- a/code/modules/asset_cache/assets/research_designs.dm
+++ b/code/modules/asset_cache/assets/research_designs.dm
@@ -1,15 +1,15 @@
// Representative icons for each research design
-/datum/asset/spritesheet/research_designs
+/datum/asset/spritesheet_batched/research_designs
name = "design"
-/datum/asset/spritesheet/research_designs/create_spritesheets()
+/datum/asset/spritesheet_batched/research_designs/create_spritesheets()
for (var/datum/design/path as anything in subtypesof(/datum/design))
if(initial(path.id) == DESIGN_ID_IGNORE)
continue
var/icon_file
var/icon_state
- var/icon/I
+ var/datum/icon_transformer/transform = null
if(initial(path.research_icon) && initial(path.research_icon_state)) //If the design has an icon replacement skip the rest
icon_file = initial(path.research_icon)
@@ -18,8 +18,6 @@
if(!icon_exists(icon_file, icon_state))
stack_trace("design [path] 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(path.build_path)
@@ -38,30 +36,32 @@
if (machine)
item = machine
- // Check for GAGS support where necessary
- var/greyscale_config = initial(item.greyscale_config)
- var/greyscale_colors = initial(item.greyscale_colors)
- if (greyscale_config && greyscale_colors)
- icon_file = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors)
+ // GAGS icon short-circuit the rest of the checks
+ if (initial(item.greyscale_config) && initial(item.greyscale_colors))
+ insert_icon(initial(path.id), gags_to_universal_icon(item))
+ continue
else
icon_file = initial(item.icon)
icon_state = initial(item.icon_state)
+ if(initial(item.color))
+ transform = color_transform(initial(item.color))
if (PERFORM_ALL_TESTS(focus_only/invalid_research_designs))
if(!icon_exists(icon_file, icon_state))
stack_trace("design [path] 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))
+ if(!transform)
+ transform = new()
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)
+ transform.blend_icon(uni_icon(icon_file, screen), ICON_OVERLAY)
if (keyboard && (keyboard in all_states))
- I.Blend(icon(icon_file, keyboard, SOUTH), ICON_OVERLAY)
+ transform.blend_icon(uni_icon(icon_file, keyboard), ICON_OVERLAY)
- Insert(initial(path.id), I)
+ insert_icon(initial(path.id), uni_icon(icon_file, icon_state, transform=transform))
diff --git a/code/modules/asset_cache/assets/rtd.dm b/code/modules/asset_cache/assets/rtd.dm
index ac2f06a1aaa..0a600dffe6b 100644
--- a/code/modules/asset_cache/assets/rtd.dm
+++ b/code/modules/asset_cache/assets/rtd.dm
@@ -1,14 +1,12 @@
-/datum/asset/spritesheet/rtd
+/datum/asset/spritesheet_batched/rtd
name = "rtd"
-/datum/asset/spritesheet/rtd/create_spritesheets()
- //some tiles may share the same icon but have different properties to animate that icon
- //so we keep track of what icons we registered
+/datum/asset/spritesheet_batched/rtd/create_spritesheets()
var/list/registered = list()
for(var/main_root in GLOB.floor_designs)
for(var/sub_category in GLOB.floor_designs[main_root])
- for(var/list/design in GLOB.floor_designs[main_root][sub_category])
+ for(var/list/design in GLOB.floor_designs[main_root][sub_category])
if(!design["datum"])
populate_rtd_datums()
var/datum/tile_info/tile_data = design["datum"]
@@ -17,5 +15,5 @@
var/sprite_name = sanitize_css_class_name("[tile_data.icon_file]-[tile_data.icon_state]-[dir2text(direction)]")
if(registered[sprite_name])
continue
- Insert(sprite_name, icon(tile_data.icon_file, tile_data.icon_state, direction))
+ insert_icon(sprite_name, uni_icon(tile_data.icon_file, tile_data.icon_state, direction))
registered[sprite_name] = TRUE
diff --git a/code/modules/asset_cache/assets/seeds.dm b/code/modules/asset_cache/assets/seeds.dm
index 4d48cd90f30..8def22ee44b 100644
--- a/code/modules/asset_cache/assets/seeds.dm
+++ b/code/modules/asset_cache/assets/seeds.dm
@@ -1,14 +1,13 @@
-/datum/asset/spritesheet/seeds
+/datum/asset/spritesheet_batched/seeds
name = "seeds"
-/datum/asset/spritesheet/seeds/create_spritesheets()
+/datum/asset/spritesheet_batched/seeds/create_spritesheets()
var/list/id_list = list()
- for (var/path in subtypesof(/obj/item/seeds))
- var/obj/item/seeds/seed_type = path
+ for (var/obj/item/seeds/seed_type as anything in subtypesof(/obj/item/seeds))
var/icon = initial(seed_type.icon)
var/icon_state = initial(seed_type.icon_state)
var/id = sanitize_css_class_name("[icon][icon_state]")
if(id in id_list) //no dupes
continue
id_list += id
- Insert(id, icon, icon_state)
+ insert_icon(id, uni_icon(icon, icon_state))
diff --git a/code/modules/asset_cache/assets/sheetmaterials.dm b/code/modules/asset_cache/assets/sheetmaterials.dm
index 037a7e4b4fc..408239edfd0 100644
--- a/code/modules/asset_cache/assets/sheetmaterials.dm
+++ b/code/modules/asset_cache/assets/sheetmaterials.dm
@@ -1,6 +1,6 @@
-/datum/asset/spritesheet/sheetmaterials
+/datum/asset/spritesheet_batched/sheetmaterials
name = "sheetmaterials"
-/datum/asset/spritesheet/sheetmaterials/create_spritesheets()
- InsertAll("", 'icons/obj/stack_objects.dmi')
+/datum/asset/spritesheet_batched/sheetmaterials/create_spritesheets()
+ insert_all_icons("", 'icons/obj/stack_objects.dmi')
diff --git a/code/modules/asset_cache/assets/supplypods.dm b/code/modules/asset_cache/assets/supplypods.dm
index 3807c080f62..86c4fdb67a0 100644
--- a/code/modules/asset_cache/assets/supplypods.dm
+++ b/code/modules/asset_cache/assets/supplypods.dm
@@ -1,27 +1,27 @@
-/datum/asset/spritesheet/supplypods
+/datum/asset/spritesheet_batched/supplypods
name = "supplypods"
-/datum/asset/spritesheet/supplypods/create_spritesheets()
+/datum/asset/spritesheet_batched/supplypods/create_spritesheets()
for (var/datum/pod_style/style as anything in typesof(/datum/pod_style))
if (ispath(style, /datum/pod_style/seethrough))
- Insert("pod_asset[style::id]", icon('icons/obj/supplypods.dmi' , "seethrough-icon"))
+ insert_icon("pod_asset[style::id]", uni_icon('icons/obj/supplypods.dmi' , "seethrough-icon"))
continue
var/base = style::icon_state
if (!base)
- Insert("pod_asset[style::id]", icon('icons/obj/supplypods.dmi', "invisible-icon"))
+ insert_icon("pod_asset[style::id]", uni_icon('icons/obj/supplypods.dmi', "invisible-icon"))
continue
- var/icon/podIcon = icon('icons/obj/supplypods.dmi', base)
+ var/datum/universal_icon/pod_icon = uni_icon('icons/obj/supplypods.dmi', base, SOUTH)
var/door = style::has_door
if (door)
door = "[base]_door"
- podIcon.Blend(icon('icons/obj/supplypods.dmi', door), ICON_OVERLAY)
+ pod_icon.blend_icon(uni_icon('icons/obj/supplypods.dmi', door), ICON_OVERLAY)
var/shape = style::shape
if (shape == POD_SHAPE_NORMAL)
var/decal = style::decal_icon
if (decal)
- podIcon.Blend(icon('icons/obj/supplypods.dmi', decal), ICON_OVERLAY)
+ pod_icon.blend_icon(uni_icon('icons/obj/supplypods.dmi', decal), ICON_OVERLAY)
var/glow = style::glow_color
if (glow)
glow = "pod_glow_[glow]"
- podIcon.Blend(icon('icons/obj/supplypods.dmi', glow), ICON_OVERLAY)
- Insert("pod_asset[style::id]", podIcon)
+ pod_icon.blend_icon(uni_icon('icons/obj/supplypods.dmi', glow), ICON_OVERLAY)
+ insert_icon("pod_asset[style::id]", pod_icon)
diff --git a/code/modules/asset_cache/assets/tcomms.dm b/code/modules/asset_cache/assets/tcomms.dm
index fdbab5f4c9b..62863a6f705 100644
--- a/code/modules/asset_cache/assets/tcomms.dm
+++ b/code/modules/asset_cache/assets/tcomms.dm
@@ -1,12 +1,12 @@
-/datum/asset/spritesheet/telecomms
+/datum/asset/spritesheet_batched/telecomms
name = "tcomms"
-/datum/asset/spritesheet/telecomms/create_spritesheets()
+/datum/asset/spritesheet_batched/telecomms/create_spritesheets()
var/list/inserted_states = list() // No need to send entire `telecomms.dmi`.
for(var/obj/machinery/telecomms/machine as anything in subtypesof(/obj/machinery/telecomms))
var/icon_state = machine::icon_state
if(icon_state in inserted_states)
continue
- Insert(icon_state, machine::icon, icon_state)
+ insert_icon(icon_state, uni_icon(machine::icon, icon_state))
inserted_states += icon_state
diff --git a/code/modules/asset_cache/assets/vending.dm b/code/modules/asset_cache/assets/vending.dm
index c898cc3eddc..8cd37d4f8a6 100644
--- a/code/modules/asset_cache/assets/vending.dm
+++ b/code/modules/asset_cache/assets/vending.dm
@@ -1,10 +1,10 @@
-/datum/asset/spritesheet/vending
+/datum/asset/spritesheet_batched/vending
name = "vending"
-/datum/asset/spritesheet/vending/create_spritesheets()
+/datum/asset/spritesheet_batched/vending/create_spritesheets()
// initialising the list of items we need
var/target_items = list()
- for(var/obj/machinery/vending/vendor as anything in typesof(/obj/machinery/vending))
+ for(var/obj/machinery/vending/vendor as anything in subtypesof(/obj/machinery/vending))
vendor = new vendor() // It seems `initial(list var)` has nothing. need to make a type.
target_items |= vendor.products
target_items |= vendor.premium
@@ -16,21 +16,21 @@
if (!ispath(item, /atom))
continue
- var/icon_file
var/icon_state = initial(item.icon_state)
- var/icon_color = initial(item.color)
- // GAGS icons must be pregenerated
- if(initial(item.greyscale_config) && initial(item.greyscale_colors))
- icon_file = SSgreyscale.GetColoredIconByType(initial(item.greyscale_config), initial(item.greyscale_colors))
- // Colored atoms must be pregenerated
- else if(icon_color && icon_state)
- icon_file = initial(item.icon)
+ if(ispath(item, /obj))
+ var/obj/obj_atom = item
+ if(initial(obj_atom.icon_state_preview))
+ icon_state = initial(obj_atom.icon_state_preview)
+ var/has_gags = initial(item.greyscale_config) && initial(item.greyscale_colors)
+ var/has_color = initial(item.color) && icon_state
+ // GAGS and colored icons must be pregenerated
// Otherwise we can rely on DMIcon, so skip it to save init time
- else
+ if(!has_gags && !has_color)
continue
if (PERFORM_ALL_TESTS(focus_only/invalid_vending_machine_icon_states))
- if (!icon_exists(icon_file, icon_state))
+ if (!has_gags && !icon_exists(initial(item.icon), icon_state))
+ var/icon_file = initial(item.icon)
var/icon_states_string
for (var/an_icon_state in icon_states(icon_file))
if (!icon_states_string)
@@ -41,10 +41,5 @@
stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)]([text_ref(icon_state)]), icon_states=[icon_states_string]")
continue
- var/icon/produced = icon(icon_file, icon_state, SOUTH)
- if (!isnull(icon_color) && icon_color != COLOR_WHITE)
- produced.Blend(icon_color, ICON_MULTIPLY)
-
var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
-
- Insert(imgid, produced)
+ insert_icon(imgid, get_display_icon_for(item))
diff --git a/code/modules/asset_cache/spritesheet/batched/batched_spritesheet.dm b/code/modules/asset_cache/spritesheet/batched/batched_spritesheet.dm
new file mode 100644
index 00000000000..9256231c0e8
--- /dev/null
+++ b/code/modules/asset_cache/spritesheet/batched/batched_spritesheet.dm
@@ -0,0 +1,365 @@
+#define SPR_SIZE "size_id"
+#define SPR_IDX "position"
+#define CACHE_WAIT "wait"
+#define CACHE_INVALID TRUE
+#define CACHE_VALID FALSE
+/// This is used to invalidate the cache if something changes on the DM side. For example, if the CSS generator was changed.
+#define SPRITESHEET_SYSTEM_VERSION 1
+
+/datum/asset/spritesheet_batched
+ _abstract = /datum/asset/spritesheet_batched
+ var/name
+ /// list("32x32")
+ var/list/sizes = list()
+ /// "foo_bar" -> list("32x32", 5, entry_obj)
+ var/list/sprites = list()
+
+ // "foo_bar" -> entry_obj
+ var/list/entries = list()
+
+ /// JSON encoded version of entries.
+ var/entries_json = null
+
+ /// If this spritesheet exists in a completed state.
+ var/fully_generated = FALSE
+
+ /// If this asset should be fully loaded on new
+ /// Defaults to false so we can process this stuff nicely
+ var/load_immediately = FALSE
+ /// If we should avoid propogating 'invalid dir' errors from rust-g. Because sometimes, you just don't know what dirs are valid.
+ var/ignore_dir_errors = FALSE
+
+ /// Forces use of the smart cache. This is for unit tests, please respect the config <3
+ var/force_cache = FALSE
+
+ /// If there is currently an async job, its ID
+ var/job_id = null
+ /// If there is currently an async cache job, its ID.
+ var/cache_job_id = null
+
+ // Fields to store async cache task inputs.
+ var/cache_data = null
+ var/cache_sizes_data = null
+ var/cache_sprites_data = null
+ var/cache_input_hash = null
+ var/cache_dmi_hashes = null
+ var/cache_dmi_hashes_json = null
+ /// Used to prevent async cache refresh jobs from looping on failure.
+ var/cache_result = null
+
+/datum/asset/spritesheet_batched/proc/should_load_immediately()
+#ifdef DO_NOT_DEFER_ASSETS
+ return TRUE
+#else
+ return load_immediately
+#endif
+
+/// Returns true if the cache should be invalidated/doesn't exist.
+/datum/asset/spritesheet_batched/should_refresh(yield)
+ . = CACHE_INVALID // in the case of any errors, we need to regenerate.
+ if(!fexists("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.[name].json"))
+ return CACHE_INVALID
+ if(!fexists("data/spritesheets/spritesheet_[name].css"))
+ return CACHE_INVALID
+ if(isnull(cache_data) || isnull(cache_dmi_hashes_json))
+ cache_data = rustg_file_read("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.[name].json")
+ if(!findtext(cache_data, "{", 1, 2)) // cache isn't valid JSON
+ log_asset("Cache for spritesheet_[name] was not valid JSON. This is abnormal. Likely tampered with or IO failure.")
+ return CACHE_INVALID
+ var/cache_json = json_decode(cache_data)
+ // Best to invalidate if rustg updates, since the way icons are handled can change.
+ var/cached_rustg_version = cache_json["rustg_version"]
+ if(isnull(cached_rustg_version))
+ log_asset("Cache for spritesheet_[name] did not contain a rustg_version!")
+ return CACHE_INVALID
+ var/rustg_version = rustg_get_version()
+ if(cached_rustg_version != rustg_version)
+ log_asset("Invalidated cache for spritesheet_[name] due to rustg updating from [cached_rustg_version] to [rustg_version].")
+ return CACHE_INVALID
+ // Invalidate cache if the DM version changes
+ var/cached_dm_version = cache_json["dm_version"]
+ if(isnull(cached_dm_version))
+ log_asset("Cache for spritesheet_[name] did not contain a dm_version!")
+ return CACHE_INVALID
+ if(cached_dm_version != SPRITESHEET_SYSTEM_VERSION)
+ log_asset("Invalidated cache for spritesheet_[name] due to DM spritesheet system updating from [cached_dm_version] to [SPRITESHEET_SYSTEM_VERSION].")
+ return CACHE_INVALID
+ cache_sizes_data = cache_json["sizes"]
+ cache_sprites_data = cache_json["sprites"]
+ cache_input_hash = cache_json["input_hash"]
+ cache_dmi_hashes = cache_json["dmi_hashes"]
+ if(!length(cache_dmi_hashes) || !length(cache_input_hash) || !length(cache_sizes_data) || !length(cache_sprites_data))
+ log_asset("Cache for spritesheet_[name] did not contain the correct data. This is abnormal. Likely tampered with.")
+ return CACHE_INVALID
+ cache_dmi_hashes_json = json_encode(cache_dmi_hashes)
+ var/data_out
+
+ if(yield || !isnull(cache_job_id))
+ if(isnull(cache_job_id))
+ cache_job_id = rustg_iconforge_cache_valid_async(cache_input_hash, cache_dmi_hashes_json, entries_json)
+ . = CACHE_WAIT // if we return during this, WAIT!!
+ UNTIL((data_out = rustg_iconforge_check(cache_job_id)) != RUSTG_JOB_NO_RESULTS_YET)
+ cache_job_id = null
+ . = CACHE_INVALID // reset back to normal, invalid on CRASH
+ else
+ data_out = rustg_iconforge_cache_valid(cache_input_hash, cache_dmi_hashes_json, entries_json)
+ if (data_out == RUSTG_JOB_ERROR)
+ CRASH("Spritesheet [name] cache JOB PANIC")
+ else if(!findtext(data_out, "{", 1, 2))
+ rustg_file_write(cache_data, "[GLOB.log_directory]/spritesheet_cache_debug.[name].json")
+ rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json")
+ CRASH("Spritesheet [name] cache check UNKNOWN ERROR: [data_out]")
+ var/result = json_decode(data_out)
+ var/fail = result["fail_reason"]
+ if(length(fail) || result["result"] != "1")
+ if(findtextEx(fail, "ERROR:"))
+ CRASH("Spritesheet [name] cache check UNKNOWN [fail]")
+ log_asset("Invalidated cache for spritesheet_[name]: [fail]")
+ return CACHE_INVALID
+ // Populate the sizes and sprites list.
+ sizes = cache_sizes_data
+ sprites = cache_sprites_data
+ log_asset("Validated cache for spritesheet_[name]!")
+ return CACHE_VALID
+
+/datum/asset/spritesheet_batched/proc/insert_icon(sprite_name, datum/universal_icon/entry)
+ if(!istext(sprite_name) || !length(sprite_name))
+ CRASH("Invalid sprite_name \"[sprite_name]\" given to insert_icon()! Providing non-strings will break icon generation.")
+ if(!istype(entry))
+ CRASH("Invalid type provided to insert_icon()! Value: [entry] (type: [entry?.type])")
+ entries[sprite_name] = entry.to_list()
+
+/datum/asset/spritesheet_batched/register()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ if (!name)
+ CRASH("spritesheet [type] cannot register without a name")
+
+ // Create our input data first, so we can compare to the cache.
+ create_spritesheets()
+
+ if(should_load_immediately())
+ realize_spritesheets(yield = FALSE)
+ else
+ SSasset_loading.queue_asset(src)
+
+/datum/asset/spritesheet_batched/unregister()
+ CRASH("unregister() called on batched spritesheet! Bad!")
+
+/// Call insert_icon or insert_all_icons here, building a spritesheet!
+/datum/asset/spritesheet_batched/proc/create_spritesheets()
+ CRASH("create_spritesheets() not implemented for [type]!")
+
+/datum/asset/spritesheet_batched/proc/insert_all_icons(prefix, icon/I, list/directions, prefix_with_dirs = TRUE)
+ 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 && prefix_with_dirs) ? "[dir2text(direction)]-" : ""
+ insert_icon("[prefix][prefix2][icon_state_name]", uni_icon(I, icon_state_name, direction))
+
+/datum/asset/spritesheet_batched/proc/realize_spritesheets(yield)
+ if(fully_generated)
+ return
+ if(!length(entries))
+ CRASH("Spritesheet [name] ([type]) is empty! What are you doing?")
+
+ if(isnull(entries_json))
+ entries_json = json_encode(entries)
+
+ if(isnull(cache_result))
+ cache_result = should_refresh(yield)
+ if(cache_result == CACHE_WAIT) // sleep interrupted by MC. We'll get queried again later.
+ cache_result = null
+ return
+
+ // read_from_cache returns false if config is disabled, otherwise it fully loads the spritesheet.
+ if (cache_result == CACHE_VALID && read_from_cache())
+ SSasset_loading.dequeue_asset(src)
+ fully_generated = TRUE
+ return
+ // Remove the cache, since it's invalid if we get to this point.
+ fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.[name].json")
+
+ var/do_cache = CONFIG_GET(flag/smart_cache_assets) || force_cache
+ var/data_out
+ if(yield || !isnull(job_id))
+ if(isnull(job_id))
+ job_id = rustg_iconforge_generate_async("data/spritesheets/", name, entries_json, do_cache)
+ UNTIL((data_out = rustg_iconforge_check(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
+ else
+ data_out = rustg_iconforge_generate("data/spritesheets/", name, entries_json, do_cache)
+ if (data_out == RUSTG_JOB_ERROR)
+ CRASH("Spritesheet [name] JOB PANIC")
+ else if(!findtext(data_out, "{", 1, 2))
+ rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json")
+ CRASH("Spritesheet [name] UNKNOWN ERROR: [data_out]")
+ var/data = json_decode(data_out)
+ sizes = data["sizes"]
+ sprites = data["sprites"]
+ var/input_hash = data["sprites_hash"]
+ var/dmi_hashes = data["dmi_hashes"] // this only contains values if do_cache is TRUE.
+
+ for(var/size_id in sizes)
+ var/png_name = "[name]_[size_id].png"
+ var/file_directory = "data/spritesheets/[png_name]"
+ var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_directory)
+ SSassets.transport.register_asset(png_name, fcopy_rsc(file_directory), file_hash)
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = png_name, file_location = file_directory)
+ var/css_name = "spritesheet_[name].css"
+ var/file_directory = "data/spritesheets/[css_name]"
+
+ fdel(file_directory)
+ var/css = generate_css()
+ rustg_file_write(css, file_directory)
+ var/css_hash = rustg_hash_string(RUSTG_HASH_MD5, css)
+ SSassets.transport.register_asset(css_name, fcopy_rsc(file_directory), file_hash=css_hash)
+
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = css_name, file_location = file_directory)
+
+ if (do_cache)
+ write_cache_meta(input_hash, dmi_hashes)
+ fully_generated = TRUE
+ // If we were ever in there, remove ourselves
+ SSasset_loading.dequeue_asset(src)
+ if(data["error"] && !(ignore_dir_errors && findtext(data["error"], "is not in the set of valid dirs")))
+ CRASH("Error during spritesheet generation for [name]: [data["error"]]")
+
+/datum/asset/spritesheet_batched/queued_generation()
+ realize_spritesheets(yield = TRUE)
+
+/datum/asset/spritesheet_batched/ensure_ready()
+ if(!fully_generated)
+ realize_spritesheets(yield = FALSE)
+ return ..()
+
+/datum/asset/spritesheet_batched/send(client/client)
+ if (!name)
+ return
+
+ var/all = list("spritesheet_[name].css")
+ for(var/size_id in sizes)
+ all += "[name]_[size_id].png"
+ . = SSassets.transport.send_assets(client, all)
+
+/datum/asset/spritesheet_batched/get_url_mappings()
+ if (!name)
+ return
+
+ . = list("spritesheet_[name].css" = SSassets.transport.get_asset_url("spritesheet_[name].css"))
+ for(var/size_id in sizes)
+ .["[name]_[size_id].png"] = SSassets.transport.get_asset_url("[name]_[size_id].png")
+
+/datum/asset/spritesheet_batched/proc/generate_css()
+ var/list/out = list()
+
+ for (var/size_id in sizes)
+ var/size_split = splittext(size_id, "x")
+ var/width = text2num(size_split[1])
+ var/height = text2num(size_split[2])
+ out += ".[name][size_id]{display:inline-block;width:[width]px;height:[height]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat: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_split = splittext(size_id, "x")
+ var/width = text2num(size_split[1])
+ var/x = idx * width
+ var/y = 0
+
+ out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
+
+ return out.Join("\n")
+
+/datum/asset/spritesheet_batched/proc/read_from_cache()
+ if(!CONFIG_GET(flag/smart_cache_assets) && !force_cache)
+ return FALSE
+ // this is already guaranteed to exist.
+ var/css_name = "spritesheet_[name].css"
+ var/css_file_directory = "data/spritesheets/[css_name]"
+
+ // sizes gets filled during should_refresh()
+ for(var/size_id in sizes)
+ var/fname = "data/spritesheets/[name]_[size_id].png"
+ if(!fexists(fname))
+ return FALSE
+
+ var/css_hash = rustg_hash_file(RUSTG_HASH_MD5, css_file_directory)
+ SSassets.transport.register_asset(css_name, fcopy_rsc(css_file_directory), file_hash=css_hash)
+ for(var/size_id in sizes)
+ var/fname = "data/spritesheets/[name]_[size_id].png"
+ var/hash = rustg_hash_file(RUSTG_HASH_MD5, fname)
+ SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(fname), file_hash=hash)
+
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = css_name, file_location = css_file_directory)
+
+ return TRUE
+
+/// Returns the URL to put in the background:url of the CSS asset
+/datum/asset/spritesheet_batched/proc/get_background_url(asset)
+ return SSassets.transport.get_asset_url(asset)
+
+/datum/asset/spritesheet_batched/proc/write_cache_meta(input_hash, dmi_hashes)
+ var/list/cache_data = list(
+ "input_hash" = input_hash,
+ "dmi_hashes" = dmi_hashes,
+ "sizes" = sizes,
+ "sprites" = sprites,
+ "rustg_version" = rustg_get_version(),
+ "dm_version" = SPRITESHEET_SYSTEM_VERSION,
+ )
+ rustg_file_write(json_encode(cache_data), "[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.[name].json")
+
+/**
+ * Third party helpers
+ * ===================
+ */
+
+/datum/asset/spritesheet_batched/proc/css_tag()
+ return {""}
+
+/datum/asset/spritesheet_batched/proc/css_filename()
+ return SSassets.transport.get_asset_url("spritesheet_[name].css")
+
+/datum/asset/spritesheet_batched/proc/icon_tag(sprite_name)
+ var/sprite = sprites[sprite_name]
+ if (!sprite)
+ return null
+ var/size_id = sprite[SPR_SIZE]
+ return ""
+
+/datum/asset/spritesheet_batched/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]"
+
+/**
+ * Returns the size class (ex design32x32) for a given sprite's icon
+ *
+ * Arguments:
+ * * sprite_name - The sprite to get the size of
+ */
+/datum/asset/spritesheet_batched/proc/icon_size_id(sprite_name)
+ var/sprite = sprites[sprite_name]
+ if (!sprite)
+ return null
+ var/size_id = sprite[SPR_SIZE]
+ return "[name][size_id]"
+
+#undef SPR_SIZE
+#undef SPR_IDX
+#undef CACHE_WAIT
+#undef CACHE_INVALID
+#undef CACHE_VALID
+#undef SPRITESHEET_SYSTEM_VERSION
diff --git a/code/modules/asset_cache/spritesheet/batched/universal_icon.dm b/code/modules/asset_cache/spritesheet/batched/universal_icon.dm
new file mode 100644
index 00000000000..08c37646459
--- /dev/null
+++ b/code/modules/asset_cache/spritesheet/batched/universal_icon.dm
@@ -0,0 +1,374 @@
+/// This is intended to replace /icon, allowing rustg to generate icons much faster than DM can at scale.
+/// Construct these with the uni_icon() proc, in the same manner as BYOND's icon() proc.
+/// Additionally supports a number of transform procs (lowercase, rather than BYOND's uppercase)
+/// such as Crop, Scale, and Blend (as blend_icon/blend_color).
+/datum/universal_icon
+ var/icon/icon_file
+ var/icon_state
+ var/dir
+ var/frame
+ var/datum/icon_transformer/transform
+
+/// Don't instantiate these yourself, use uni_icon.
+/datum/universal_icon/New(icon/icon_file, icon_state="", dir=SOUTH, frame=1, datum/icon_transformer/transform=null, color=null)
+ #ifdef UNIT_TESTS
+ // This check is kinda slow and shouldn't fail unless a developer makes a mistake. So it'll get caught in unit tests.
+ if(!isicon(icon_file) || !isfile(icon_file) || "[icon_file]" == "/icon" || !length("[icon_file]"))
+ // bad! use 'icons/path_to_dmi.dmi' format only
+ CRASH("FATAL: universal_icon was provided icon_file: [icon_file] - icons provided to batched spritesheets MUST be DMI files, they cannot be /image, /icon, or other runtime generated icons.")
+ #endif
+ src.icon_file = icon_file
+ src.icon_state = icon_state
+ src.dir = dir
+ src.frame = frame
+ if(isnull(transform) && !isnull(color) && uppertext(color) != "#FFFFFF")
+ var/datum/icon_transformer/T = new()
+ if(color)
+ T.blend_color(color, ICON_MULTIPLY)
+ src.transform = T
+ else if(!isnull(transform))
+ src.transform = transform
+ else // null = empty list
+ src.transform = null
+
+/datum/universal_icon/proc/copy()
+ RETURN_TYPE(/datum/universal_icon)
+ var/datum/universal_icon/new_icon = new(icon_file, icon_state, dir, frame)
+ if(!isnull(src.transform))
+ new_icon.transform = src.transform.copy()
+ return new_icon
+
+/datum/universal_icon/proc/blend_color(color, blend_mode)
+ if(!transform)
+ transform = new
+ transform.blend_color(color, blend_mode)
+ return src
+
+/datum/universal_icon/proc/blend_icon(datum/universal_icon/icon_object, blend_mode)
+ if(!transform)
+ transform = new
+ transform.blend_icon(icon_object, blend_mode)
+ return src
+
+/datum/universal_icon/proc/scale(width, height)
+ if(!transform)
+ transform = new
+ transform.scale(width, height)
+ return src
+
+/datum/universal_icon/proc/crop(x1, y1, x2, y2)
+ if(!transform)
+ transform = new
+ transform.crop(x1, y1, x2, y2)
+ return src
+
+/// Internally performs a crop.
+/datum/universal_icon/proc/shift(dir, amount, icon_width, icon_height)
+ if(!transform)
+ transform = new
+ var/list/offsets = dir2offset(dir)
+ var/shift_x = -offsets[1] * amount
+ var/shift_y = -offsets[2] * amount
+ transform.crop(1 + shift_x, 1 + shift_y, icon_width + shift_x, icon_height + shift_y)
+ return src
+
+/// Internally performs a color blend.
+/// Amount ranges from 0-1 (100% opacity)
+/datum/universal_icon/proc/change_opacity(amount)
+ if(!transform)
+ transform = new
+ transform.blend_color("#ffffff[num2hex(clamp(amount, 0, 1) * 255, 2)]", ICON_MULTIPLY)
+ return src
+
+
+/datum/universal_icon/proc/to_list()
+ RETURN_TYPE(/list)
+ return list("icon_file" = "[icon_file]", "icon_state" = icon_state, "dir" = dir, "frame" = frame, "transform" = !isnull(transform) ? transform.to_list() : list())
+
+/proc/universal_icon_from_list(list/input_in)
+ RETURN_TYPE(/datum/universal_icon)
+ var/list/input = input_in.Copy() // copy, since icon_transformer_from_list will mutate the list.
+ return uni_icon(input["icon_file"], input["icon_state"], input["dir"], input["frame"], icon_transformer_from_list(input["transform"]))
+
+/datum/universal_icon/proc/to_json()
+ return json_encode(to_list())
+
+/datum/universal_icon/proc/to_icon()
+ RETURN_TYPE(/icon)
+ var/icon/self = icon(src.icon_file, src.icon_state, dir=src.dir, frame=src.frame)
+ if(istype(src.transform))
+ src.transform.apply(self)
+ return self
+
+/datum/icon_transformer
+ var/list/transforms = null
+
+/datum/icon_transformer/New()
+ transforms = list()
+
+/// Applies the contained set of transforms to an icon
+/datum/icon_transformer/proc/apply(icon/target)
+ RETURN_TYPE(/icon)
+ for(var/transform in src.transforms)
+ switch(transform["type"])
+ if(RUSTG_ICONFORGE_BLEND_COLOR)
+ target.Blend(transform["color"], transform["blend_mode"])
+ if(RUSTG_ICONFORGE_BLEND_ICON)
+ var/datum/universal_icon/icon_object = transform["icon"]
+ if(!istype(icon_object))
+ stack_trace("Invalid icon found in icon transformer during apply()! [icon_object]")
+ continue
+ target.Blend(icon_object.to_icon(), transform["blend_mode"])
+ if(RUSTG_ICONFORGE_SCALE)
+ target.Scale(transform["width"], transform["height"])
+ if(RUSTG_ICONFORGE_CROP)
+ target.Crop(transform["x1"], transform["y1"], transform["x2"], transform["y2"])
+ return target
+
+/datum/icon_transformer/proc/copy()
+ RETURN_TYPE(/datum/icon_transformer)
+ var/datum/icon_transformer/new_transformer = new()
+ new_transformer.transforms = list()
+ for(var/transform in src.transforms)
+ new_transformer.transforms += list(deep_copy_list_alt(transform))
+ return new_transformer
+
+/datum/icon_transformer/proc/blend_color(color, blend_mode)
+ #ifdef UNIT_TESTS
+ if(!istext(color))
+ CRASH("Invalid color provided to blend_color: [color]")
+ if(!isnum(blend_mode))
+ CRASH("Invalid blend_mode provided to blend_color: [blend_mode]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_COLOR, "color" = color, "blend_mode" = blend_mode))
+
+/datum/icon_transformer/proc/blend_icon(datum/universal_icon/icon_object, blend_mode)
+ #ifdef UNIT_TESTS
+ // icon_object's type is checked later in to_list
+ if(!isnum(blend_mode))
+ CRASH("Invalid blend_mode provided to blend_icon: [blend_mode]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode))
+
+/datum/icon_transformer/proc/scale(width, height)
+ #ifdef UNIT_TESTS
+ if(!isnum(width) || !isnum(height))
+ CRASH("Invalid arguments provided to scale: [width],[height]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_SCALE, "width" = width, "height" = height))
+
+/datum/icon_transformer/proc/crop(x1, y1, x2, y2)
+ #ifdef UNIT_TESTS
+ if(!isnum(x1) || !isnum(y1) || !isnum(x2) || !isnum(y2))
+ CRASH("Invalid arguments provided to crop: [x1],[y1],[x2],[y2]")
+ #endif
+ transforms += list(list("type" = RUSTG_ICONFORGE_CROP, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2))
+
+/// Recursively converts all contained [/datum/universal_icon]s and their associated [/datum/icon_transformer]s into list form so the transforms can be JSON encoded.
+/datum/icon_transformer/proc/to_list()
+ RETURN_TYPE(/list)
+ var/list/transforms_out = list()
+ var/list/transforms_original = src.transforms.Copy()
+ for(var/list/transform as anything in transforms_original)
+ var/list/this_transform = transform.Copy() // copy it so we don't mutate the original
+ if(transform["type"] == RUSTG_ICONFORGE_BLEND_ICON)
+ var/datum/universal_icon/icon_object = this_transform["icon"]
+ if(!istype(icon_object))
+ stack_trace("Invalid icon found in icon transformer during to_list()! [icon_object]")
+ continue
+ // This mutates the inner transform list!!! Make sure it only runs on copies.
+ this_transform["icon"] = icon_object.to_list()
+ transforms_out += list(this_transform)
+ return transforms_out
+
+/// Reverse operation of /datum/icon_transformer/to_list()
+/proc/icon_transformer_from_list(list/input)
+ RETURN_TYPE(/datum/icon_transformer)
+ var/list/transforms = list()
+ for(var/list/transform as anything in input)
+ // don't mutate the input :(
+ var/this_transform = transform.Copy()
+ if(transform["type"] == RUSTG_ICONFORGE_BLEND_ICON)
+ this_transform["icon"] = universal_icon_from_list(transform["icon"])
+ transforms += list(this_transform)
+ var/datum/icon_transformer/transformer = new()
+ transformer.transforms = transforms
+ return transformer
+
+/// Constructs a transformer, with optional color multiply pre-added.
+/proc/color_transform(color=null)
+ RETURN_TYPE(/datum/icon_transformer)
+ var/datum/icon_transformer/transform = new()
+ if(color)
+ transform.blend_color(color, ICON_MULTIPLY)
+ return transform
+
+/// Converts a GAGS atom to a universal icon by generating blend operations.
+/proc/gags_to_universal_icon(atom/path)
+ RETURN_TYPE(/datum/universal_icon)
+ if(!ispath(path, /atom) || !initial(path.greyscale_config) || !initial(path.greyscale_colors))
+ CRASH("gags_to_universal_icon() received an invalid path of \"[path]\"!")
+ var/datum/greyscale_config/config = initial(path.greyscale_config)
+ var/colors = initial(path.greyscale_colors)
+ var/datum/universal_icon/entry = SSgreyscale.GetColoredIconByTypeUniversalIcon(config, colors, initial(path.icon_state))
+ return entry
+
+/// Gets the relevant universal icon for an atom, when displayed in TGUI. (see: icon_state_preview)
+/// Supports GAGS items and colored items.
+/proc/get_display_icon_for(atom/atom_path)
+ if (!ispath(atom_path, /atom))
+ return FALSE
+ var/icon_file = initial(atom_path.icon)
+ var/icon_state = initial(atom_path.icon_state)
+ if(initial(atom_path.greyscale_config) && initial(atom_path.greyscale_colors))
+ return gags_to_universal_icon(atom_path)
+ if(ispath(atom_path, /obj))
+ var/obj/obj_path = atom_path
+ if(initial(obj_path.icon_state_preview))
+ icon_state = initial(obj_path.icon_state_preview)
+ return uni_icon(icon_file, icon_state, color=initial(atom_path.color))
+
+/// getFlatIcon for [/datum/universal_icon]s
+/// Only supports 32x32 icons facing south
+/// Tough luck if you want anything else
+/// Still fairly slow for complex appearances due to filesystem operations. Try to avoid using it
+/proc/get_flat_uni_icon(image/appearance, deficon, defstate, defblend, start = TRUE, parentcolor)
+ // Loop through the underlays, then overlays, sorting them into the layers list
+ #define PROCESS_OVERLAYS_OR_UNDERLAYS(flat, process, base_layer) \
+ for (var/i in 1 to process.len) { \
+ var/image/current = process[i]; \
+ if (!current) { \
+ continue; \
+ } \
+ if (current.plane != FLOAT_PLANE && current.plane != appearance.plane) { \
+ continue; \
+ } \
+ var/current_layer = current.layer; \
+ if (current_layer < 0) { \
+ if (current_layer <= -1000) { \
+ return flat; \
+ } \
+ current_layer = base_layer + appearance.layer + current_layer / 1000; \
+ } \
+ /* If we are using topdown rendering, chop that part off so things layer together as expected */ \
+ if((current_layer >= TOPDOWN_LAYER && current_layer < EFFECTS_LAYER) || current_layer > TOPDOWN_LAYER + EFFECTS_LAYER) { \
+ current_layer -= TOPDOWN_LAYER; \
+ } \
+ for (var/index_to_compare_to in 1 to layers.len) { \
+ var/compare_to = layers[index_to_compare_to]; \
+ if (current_layer < layers[compare_to]) { \
+ layers.Insert(index_to_compare_to, current); \
+ break; \
+ } \
+ } \
+ layers[current] = current_layer; \
+ }
+
+ var/datum/universal_icon/flat = uni_icon('icons/blanks/32x32.dmi', "nothing")
+
+ if(!appearance || appearance.alpha <= 0)
+ return flat
+
+ if(start)
+ if(!deficon)
+ deficon = appearance.icon
+ if(!defstate)
+ defstate = appearance.icon_state
+ if(!defblend)
+ defblend = appearance.blend_mode
+
+ var/should_display = TRUE
+ var/curicon = appearance.icon || deficon
+ var/string_curicon = "[curicon]"
+ var/curstate = appearance.icon_state || defstate
+ // Filter out 'runtime' icons (server-generated RSC cache icons)
+ // Write the icon to the filesystem so it can be used by iconforge
+ if(!isfile(curicon) || string_curicon == "/icon" || string_curicon == "/image" || !length(string_curicon))
+ var/file_path_tmp = "tmp/uni_icon-tmp-[rand(1, 999)].dmi" // this filename is temporary.
+ fcopy(curicon, file_path_tmp)
+ var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_path_tmp)
+ // Use the hash as its new filename - this allows the uni_icon to be smart cached, because the filename will be consistent between runs if the content is the same
+ var/file_path = "tmp/uni_icon-[file_hash].dmi"
+ fcopy(file_path_tmp, file_path)
+ fdel(file_path_tmp) // delete the old one
+ curicon = file(file_path)
+
+ var/curblend = appearance.blend_mode || defblend
+ var/list/curstates = icon_states(curicon)
+ if(!(curstate in curstates))
+ if("" in curstates) // BYOND defaulting functionality
+ curstate = ""
+ else
+ should_display = FALSE
+
+ if(appearance.overlays.len || appearance.underlays.len)
+ // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed
+ var/list/layers = list()
+ var/image/copy
+ if(should_display)
+ // Add the atom's icon itself, without pixel_x/y offsets.
+ copy = image(icon=curicon, icon_state=curstate, layer=appearance.layer, dir=SOUTH)
+ copy.color = appearance.color
+ copy.alpha = appearance.alpha
+ copy.blend_mode = curblend
+ layers[copy] = appearance.layer
+
+ PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.underlays, 0)
+ PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.overlays, 1)
+
+ var/datum/universal_icon/add // Icon of overlay being added
+
+ if(appearance.color)
+ if(islist(appearance.color))
+ stack_trace("Unsupported color map appearance provided to get_flat_uni_icon, ignoring it.")
+ else
+ flat.blend_color(appearance.color, ICON_MULTIPLY)
+
+ if(parentcolor && !(appearance.appearance_flags & RESET_COLOR))
+ if(islist(parentcolor))
+ stack_trace("Unsupported color map appearance provided to get_flat_uni_icon, ignoring it.")
+ else
+ flat.blend_color(parentcolor, ICON_MULTIPLY)
+
+ var/next_parentcolor = appearance.color || parentcolor
+
+ for(var/image/layer_image as anything in layers)
+ if(layer_image.alpha == 0)
+ continue
+
+ if(layer_image == copy && length("[layer_image.icon]")) // 'layer_image' is an /image based on the object being flattened, and isn't a 'runtime' icon.
+ curblend = BLEND_OVERLAY
+ add = uni_icon(layer_image.icon, layer_image.icon_state, SOUTH)
+ if(appearance.color)
+ if(islist(appearance.color))
+ stack_trace("Unsupported color map appearance provided to get_flat_uni_icon, ignoring it.")
+ else
+ add.blend_color(appearance.color, ICON_MULTIPLY)
+ else // 'layer_image' is an appearance object.
+ add = get_flat_uni_icon(layer_image, curicon, curstate, curblend, FALSE, next_parentcolor)
+ if(!add || !length(add.icon_file))
+ continue
+
+ // Blend the overlay into the flattened icon
+ flat.blend_icon(add, blendMode2iconMode(curblend))
+
+ if(appearance.alpha < 255)
+ flat.blend_color(rgb(255, 255, 255, appearance.alpha), ICON_MULTIPLY)
+
+ return flat
+
+ else if(should_display) // There's no overlays.
+ var/datum/universal_icon/final_icon = uni_icon(curicon, curstate, SOUTH)
+
+ if (appearance.alpha < 255)
+ final_icon.blend_color(rgb(255,255,255, appearance.alpha), ICON_MULTIPLY)
+
+ if (appearance.color)
+ if (islist(appearance.color))
+ stack_trace("Unsupported color map appearance provided to get_flat_uni_icon, ignoring it.")
+ else
+ final_icon.blend_color(appearance.color, ICON_MULTIPLY)
+
+ return final_icon
+
+ #undef PROCESS_OVERLAYS_OR_UNDERLAYS
diff --git a/code/modules/asset_cache/spritesheet/legacy/legacy_spritesheet.dm b/code/modules/asset_cache/spritesheet/legacy/legacy_spritesheet.dm
new file mode 100644
index 00000000000..f0d5760b9af
--- /dev/null
+++ b/code/modules/asset_cache/spritesheet/legacy/legacy_spritesheet.dm
@@ -0,0 +1,418 @@
+
+// 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
+
+/// Deprecated: Use /datum/asset/spritesheet_batched where possible
+/datum/asset/spritesheet
+ _abstract = /datum/asset/spritesheet
+ cross_round_cachable = TRUE
+ var/name
+ /// List of arguments to pass into queuedInsert
+ /// Exists so we can queue icon insertion, mostly for stuff like preferences
+ var/list/to_generate = list()
+ var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped)
+ var/list/sprites = list() // "foo_bar" -> list("32x32", 5)
+ var/list/cached_spritesheets_needed
+ var/generating_cache = FALSE
+ var/fully_generated = FALSE
+ /// If this asset should be fully loaded on new
+ /// Defaults to false so we can process this stuff nicely
+ var/load_immediately = FALSE
+ // Kept in state so that the result is the same, even when the files are created, for this run
+ VAR_PRIVATE/should_refresh = null
+
+/datum/asset/spritesheet/proc/should_load_immediately()
+#ifdef DO_NOT_DEFER_ASSETS
+ return TRUE
+#else
+ return load_immediately
+#endif
+
+
+/datum/asset/spritesheet/should_refresh()
+ if (..())
+ return TRUE
+
+ if (isnull(should_refresh))
+ // `fexists` seems to always fail on static-time
+ should_refresh = !fexists(css_cache_filename()) || !fexists(data_cache_filename())
+
+ return should_refresh
+
+/datum/asset/spritesheet/unregister()
+ SSassets.transport.unregister_asset("spritesheet_[name].css")
+ if(length(sizes))
+ for(var/size_id in sizes)
+ SSassets.transport.unregister_asset("[name]_[size_id].png")
+ else
+ for(var/sheet in cached_spritesheets_needed)
+ SSassets.transport.unregister_asset(sheet)
+
+/datum/asset/spritesheet/regenerate()
+ unregister()
+ sprites = list()
+ fdel("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css")
+ for(var/sheet in cached_spritesheets_needed)
+ fdel("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[sheet].png")
+ fdel("data/spritesheets/spritesheet_[name].css")
+ for(var/size_id in sizes)
+ fdel("data/spritesheets/[name]_[size_id].png")
+ sizes = list()
+ to_generate = list()
+ cached_serialized_url_mappings = null
+ cached_serialized_url_mappings_transport_type = null
+ fully_generated = FALSE
+ var/old_load = load_immediately
+ load_immediately = TRUE
+ create_spritesheets()
+ realize_spritesheets(yield = FALSE)
+ load_immediately = old_load
+
+/datum/asset/spritesheet/register()
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ if (!name)
+ CRASH("spritesheet [type] cannot register without a name")
+
+ if (!should_refresh() && read_from_cache())
+ fully_generated = TRUE
+ return
+
+ // If it's cached, may as well load it now, while the loading is cheap
+ if(CONFIG_GET(flag/cache_assets) && cross_round_cachable)
+ load_immediately = TRUE
+
+ create_spritesheets()
+ if(should_load_immediately())
+ realize_spritesheets(yield = FALSE)
+ else
+ SSasset_loading.queue_asset(src)
+
+/datum/asset/spritesheet/proc/realize_spritesheets(yield)
+ if(fully_generated)
+ return
+ while(length(to_generate))
+ var/list/stored_args = to_generate[to_generate.len]
+ to_generate.len--
+ queuedInsert(arglist(stored_args))
+ if(yield && TICK_CHECK)
+ return
+
+ ensure_stripped()
+ for(var/size_id in sizes)
+ var/size = sizes[size_id]
+ var/file_path = size[SPRSZ_STRIPPED]
+ var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_path)
+ SSassets.transport.register_asset("[name]_[size_id].png", file_path, file_hash=file_hash)
+ var/css_name = "spritesheet_[name].css"
+ var/file_directory = "data/spritesheets/[css_name]"
+ fdel(file_directory)
+ var/css = generate_css()
+ rustg_file_write(css, file_directory)
+ var/css_hash = rustg_hash_string(RUSTG_HASH_MD5, css)
+ SSassets.transport.register_asset(css_name, fcopy_rsc(file_directory), file_hash=css_hash)
+
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = css_name, file_location = file_directory)
+
+ fdel(file_directory)
+
+ if (CONFIG_GET(flag/cache_assets) && cross_round_cachable)
+ write_to_cache()
+ fully_generated = TRUE
+ // If we were ever in there, remove ourselves
+ SSasset_loading.dequeue_asset(src)
+
+/datum/asset/spritesheet/queued_generation()
+ realize_spritesheets(yield = TRUE)
+
+/datum/asset/spritesheet/ensure_ready()
+ if(!fully_generated)
+ realize_spritesheets(yield = FALSE)
+ return ..()
+
+/datum/asset/spritesheet/send(client/client)
+ if (!name)
+ return
+
+ if (!should_refresh())
+ return send_from_cache(client)
+
+ var/all = list("spritesheet_[name].css")
+ for(var/size_id in sizes)
+ all += "[name]_[size_id].png"
+ . = SSassets.transport.send_assets(client, all)
+
+/datum/asset/spritesheet/get_url_mappings()
+ if (!name)
+ return
+
+ if (!should_refresh())
+ return get_cached_url_mappings()
+
+ . = list("spritesheet_[name].css" = SSassets.transport.get_asset_url("spritesheet_[name].css"))
+ for(var/size_id in sizes)
+ .["[name]_[size_id].png"] = SSassets.transport.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
+
+ // save flattened version
+ var/png_name = "[name]_[size_id].png"
+ var/file_directory = "data/spritesheets/[png_name]"
+ fcopy(size[SPRSZ_ICON], file_directory)
+ var/error = rustg_dmi_strip_metadata(file_directory)
+ if(length(error))
+ stack_trace("Failed to strip [png_name]: [error]")
+ size[SPRSZ_STRIPPED] = icon(file_directory)
+
+ // this is useful here for determining if weird sprite issues (like having a white background) are a cause of what we're doing DM-side or not since we can see the full flattened thing at-a-glance.
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = png_name, file_location = file_directory)
+
+ fdel(file_directory)
+
+/datum/asset/spritesheet/proc/generate_css()
+ var/list/out = list()
+
+ for (var/size_id in sizes)
+ var/size = sizes[size_id]
+ var/list/dimensions = get_icon_dimensions(size[SPRSZ_ICON])
+ out += ".[name][size_id]{display:inline-block;width:[dimensions["width"]]px;height:[dimensions["height"]]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat: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/list/tiny_dimensions = get_icon_dimensions(size[SPRSZ_ICON])
+ var/icon/big = size[SPRSZ_STRIPPED]
+ // big width won't be cached ever
+ var/per_line = big.Width() / tiny_dimensions["width"]
+ var/x = (idx % per_line) * tiny_dimensions["width"]
+ var/y = round(idx / per_line) * tiny_dimensions["height"]
+
+ out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
+
+ return out.Join("\n")
+
+/datum/asset/spritesheet/proc/css_cache_filename()
+ return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css"
+
+/datum/asset/spritesheet/proc/data_cache_filename()
+ return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].json"
+
+/datum/asset/spritesheet/proc/read_from_cache()
+ return read_css_from_cache() && read_data_from_cache()
+
+/datum/asset/spritesheet/proc/read_css_from_cache()
+ var/replaced_css = rustg_file_read(css_cache_filename())
+
+ var/regex/find_background_urls = regex(@"background-image:url\('%(.+?)%'\)", "g")
+ while (find_background_urls.Find(replaced_css))
+ var/asset_id = find_background_urls.group[1]
+ var/file_path = "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[asset_id]"
+ // Hashing it here is a *lot* faster.
+ var/hash = rustg_hash_file(RUSTG_HASH_MD5, file_path)
+ var/asset_cache_item = SSassets.transport.register_asset(asset_id, file_path, file_hash=hash)
+ var/asset_url = SSassets.transport.get_asset_url(asset_cache_item = asset_cache_item)
+ replaced_css = replacetext(replaced_css, find_background_urls.match, "background-image:url('[asset_url]')")
+ LAZYADD(cached_spritesheets_needed, asset_id)
+
+ var/finalized_name = "spritesheet_[name].css"
+ var/replaced_css_filename = "data/spritesheets/[finalized_name]"
+ var/css_hash = rustg_hash_string(RUSTG_HASH_MD5, replaced_css)
+ rustg_file_write(replaced_css, replaced_css_filename)
+ SSassets.transport.register_asset(finalized_name, replaced_css_filename, file_hash=css_hash)
+
+ if(CONFIG_GET(flag/save_spritesheets))
+ save_to_logs(file_name = finalized_name, file_location = replaced_css_filename)
+
+ fdel(replaced_css_filename)
+
+ return TRUE
+
+/datum/asset/spritesheet/proc/read_data_from_cache()
+ var/json = json_decode(rustg_file_read(data_cache_filename()))
+
+ if (islist(json["sprites"]))
+ sprites = json["sprites"]
+
+ return TRUE
+
+/datum/asset/spritesheet/proc/send_from_cache(client/client)
+ if (isnull(cached_spritesheets_needed))
+ stack_trace("cached_spritesheets_needed was null when sending assets from [type] from cache")
+ cached_spritesheets_needed = list()
+
+ return SSassets.transport.send_assets(client, cached_spritesheets_needed + "spritesheet_[name].css")
+
+/// Returns the URL to put in the background:url of the CSS asset
+/datum/asset/spritesheet/proc/get_background_url(asset)
+ if (generating_cache)
+ return "%[asset]%"
+ else
+ return SSassets.transport.get_asset_url(asset)
+
+/datum/asset/spritesheet/proc/write_to_cache()
+ write_css_to_cache()
+ write_data_to_cache()
+
+/datum/asset/spritesheet/proc/write_css_to_cache()
+ for (var/size_id in sizes)
+ fcopy(SSassets.cache["[name]_[size_id].png"].resource, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name]_[size_id].png")
+
+ generating_cache = TRUE
+ var/mock_css = generate_css()
+ generating_cache = FALSE
+
+ rustg_file_write(mock_css, css_cache_filename())
+
+/datum/asset/spritesheet/proc/write_data_to_cache()
+ rustg_file_write(json_encode(list(
+ "sprites" = sprites,
+ )), data_cache_filename())
+
+/datum/asset/spritesheet/proc/get_cached_url_mappings()
+ var/list/mappings = list()
+ mappings["spritesheet_[name].css"] = SSassets.transport.get_asset_url("spritesheet_[name].css")
+
+ for (var/asset_name in cached_spritesheets_needed)
+ mappings[asset_name] = SSassets.transport.get_asset_url(asset_name)
+
+ return mappings
+
+/// Override this in order to start the creation of the spritehseet.
+/// This is where all your Insert, InsertAll, etc calls should be inside.
+/datum/asset/spritesheet/proc/create_spritesheets()
+ CRASH("create_spritesheets() not implemented for [type]!")
+
+/datum/asset/spritesheet/proc/Insert(sprite_name, icon/inserted_icon, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
+ if(should_load_immediately())
+ queuedInsert(sprite_name, inserted_icon, icon_state, dir, frame, moving)
+ else
+ to_generate += list(args.Copy())
+
+/datum/asset/spritesheet/proc/queuedInsert(sprite_name, icon/inserted_icon, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
+#ifdef UNIT_TESTS
+ if (inserted_icon && icon_state && !icon_exists(inserted_icon, icon_state)) // check the base icon prior to extracting the state we want
+ stack_trace("Tried to insert nonexistent icon_state '[icon_state]' from [inserted_icon] into spritesheet [name] ([type])")
+ return
+#endif
+ inserted_icon = icon(inserted_icon, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
+ if (!inserted_icon || !length(icon_states(inserted_icon))) // that direction or state doesn't exist
+ return
+
+ var/start_usage = world.tick_usage
+
+ //any sprite modifications we want to do (aka, coloring a greyscaled asset)
+ inserted_icon = ModifyInserted(inserted_icon)
+ var/list/dimensions = get_icon_dimensions(inserted_icon)
+ var/size_id = "[dimensions["width"]]x[dimensions["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]++
+ // Icons are essentially representations of files + modifications
+ // Because of this, byond keeps them in a cache. It does this in a really dumb way tho
+ // It's essentially a FIFO queue. So after we do icon() some amount of times, our old icons go out of cache
+ // When this happens it becomes impossible to modify them, trying to do so will instead throw a
+ // "bad icon" error.
+ // What we're doing here is ensuring our icon is in the cache by refreshing it, so we can modify it w/o runtimes.
+ var/icon/sheet = size[SPRSZ_ICON]
+ var/icon/sheet_copy = icon(sheet)
+ size[SPRSZ_STRIPPED] = null
+ sheet_copy.Insert(inserted_icon, icon_state=sprite_name)
+ size[SPRSZ_ICON] = sheet_copy
+
+ sprites[sprite_name] = list(size_id, position)
+ else
+ sizes[size_id] = size = list(1, inserted_icon, null)
+ sprites[sprite_name] = list(size_id, 0)
+
+ SSblackbox.record_feedback("tally", "spritesheet_queued_insert_time", TICK_USAGE_TO_MS(start_usage), name)
+
+/**
+ * A simple proc handing the Icon for you to modify before it gets turned into an asset.
+ *
+ * Arguments:
+ * * I: icon being turned into an asset
+ */
+/datum/asset/spritesheet/proc/ModifyInserted(icon/pre_asset)
+ return pre_asset
+
+/datum/asset/spritesheet/proc/InsertAll(prefix, icon/inserted_icon, list/directions)
+ if (length(prefix))
+ prefix = "[prefix]-"
+
+ if (!directions)
+ directions = list(SOUTH)
+
+ for (var/icon_state_name in icon_states(inserted_icon))
+ for (var/direction in directions)
+ var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
+ Insert("[prefix][prefix2][icon_state_name]", inserted_icon, icon_state=icon_state_name, dir=direction)
+
+/datum/asset/spritesheet/proc/css_tag()
+ return {""}
+
+/datum/asset/spritesheet/proc/css_filename()
+ return SSassets.transport.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 {""}
+
+/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]"}
+
+/**
+ * Returns the size class (ex design32x32) for a given sprite's icon
+ *
+ * Arguments:
+ * * sprite_name - The sprite to get the size of
+ */
+/datum/asset/spritesheet/proc/icon_size_id(sprite_name)
+ var/sprite = sprites[sprite_name]
+ if (!sprite)
+ return null
+ var/size_id = sprite[SPR_SIZE]
+ return "[name][size_id]"
+
+#undef SPR_SIZE
+#undef SPR_IDX
+#undef SPRSZ_COUNT
+#undef SPRSZ_ICON
+#undef SPRSZ_STRIPPED
+
+/// Spritesheet that only uses simple PNGs and CSS keys. See `assets` variable.
+/// Deprecated: Use /datum/asset/spritesheet_batched where possible
+/datum/asset/spritesheet/simple
+ _abstract = /datum/asset/spritesheet/simple
+ /// Associative list of icon keys (CSS class names) -> PNG filepaths (single quote!)
+ /// File paths MUST be PNGs
+ var/list/assets
+
+/datum/asset/spritesheet/simple/create_spritesheets()
+ for (var/key in assets)
+ Insert(key, assets[key])
diff --git a/code/modules/asset_cache/transports/asset_transport.dm b/code/modules/asset_cache/transports/asset_transport.dm
index 62ca18fe82a..3a07160ae0a 100644
--- a/code/modules/asset_cache/transports/asset_transport.dm
+++ b/code/modules/asset_cache/transports/asset_transport.dm
@@ -1,5 +1,3 @@
-/// When sending mutiple assets, how many before we give the client a quaint little sending resources message
-#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
/// Base browse_rsc asset transport
/datum/asset_transport
@@ -59,6 +57,10 @@
SSassets.cache[asset_name] = ACI
return ACI
+/// Immediately removes an asset from the asset cache.
+/datum/asset_transport/proc/unregister_asset(asset_name)
+ SSassets.cache[asset_name] = null
+ SSassets.cache.Remove(null)
/// Returns a url for a given asset.
/// asset_name - Name of the asset.
@@ -147,7 +149,7 @@
/// Precache files without clogging up the browse() queue, used for passively sending files on connection start.
-/datum/asset_transport/proc/send_assets_slow(client/client, list/files, filerate = 6)
+/datum/asset_transport/proc/send_assets_slow(client/client, list/files, filerate = SLOW_ASSET_SEND_RATE)
var/startingfilerate = filerate
for (var/file in files)
if (!client)
diff --git a/code/modules/asset_cache/transports/webroot_transport.dm b/code/modules/asset_cache/transports/webroot_transport.dm
index 5ed987c5e3b..44724b4f34d 100644
--- a/code/modules/asset_cache/transports/webroot_transport.dm
+++ b/code/modules/asset_cache/transports/webroot_transport.dm
@@ -1,4 +1,4 @@
-/// CDN Webroot asset transport.
+/// CDN Webroot asset transport.
/datum/asset_transport/webroot
name = "CDN Webroot asset transport"
@@ -16,7 +16,7 @@
/// We also save it to the CDN webroot at this step instead of waiting for send_assets()
/// asset_name - the identifier of the asset
/// asset - the actual asset file or an asset_cache_item datum.
-/datum/asset_transport/webroot/register_asset(asset_name, asset)
+/datum/asset_transport/webroot/register_asset(asset_name, asset, file_hash, dmi_path)
. = ..()
var/datum/asset_cache_item/ACI = .
@@ -59,7 +59,7 @@
if (!islist(asset_list))
asset_list = list(asset_list)
for (var/asset_name in asset_list)
- var/datum/asset_cache_item/ACI = asset_list[asset_name]
+ var/datum/asset_cache_item/ACI = asset_list[asset_name]
if (!istype(ACI))
ACI = SSassets.cache[asset_name]
if (!ACI)
@@ -69,7 +69,7 @@
legacy_assets[asset_name] = ACI
if (length(legacy_assets))
. = ..(client, legacy_assets)
-
+
/// webroot slow asset sending - does nothing.
/datum/asset_transport/webroot/send_assets_slow(client/client, list/files, filerate)
diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm
index 5205e842c78..7ebb1413648 100644
--- a/code/modules/cargo/centcom_podlauncher.dm
+++ b/code/modules/cargo/centcom_podlauncher.dm
@@ -128,7 +128,7 @@ ADMIN_VERB(centcom_podlauncher, R_ADMIN, "Config/Launch Supplypod", "Configure a
/datum/centcom_podlauncher/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/supplypods),
+ get_asset_datum(/datum/asset/spritesheet_batched/supplypods),
)
/datum/centcom_podlauncher/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 545458537e1..f8b2a55ba4b 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -192,7 +192,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
/datum/preferences/ui_assets(mob/user)
var/list/assets = list(
- get_asset_datum(/datum/asset/spritesheet/preferences),
+ get_asset_datum(/datum/asset/spritesheet_batched/preferences),
get_asset_datum(/datum/asset/json/preferences),
)
diff --git a/code/modules/client/preferences/README.md b/code/modules/client/preferences/README.md
index 674f234d48e..ec4e2abe80d 100644
--- a/code/modules/client/preferences/README.md
+++ b/code/modules/client/preferences/README.md
@@ -142,11 +142,11 @@ Choiced preferences can generate icons. This is how the clothing/species prefere
/datum/preference/choiced/favorite_drink/icon_for(value)
switch (value)
if ("Milk")
- return icon('drinks.dmi', "milk")
+ return uni_icon('drinks.dmi', "milk")
if ("Cola")
- return icon('drinks.dmi', "cola")
+ return uni_icon('drinks.dmi', "cola")
if ("Water")
- return icon('drinks.dmi', "water")
+ return uni_icon('drinks.dmi', "water")
```
Then, change your `.tsx` file to look like:
diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm
index 2ed2405f376..3b4a688ebf3 100644
--- a/code/modules/client/preferences/_preference.dm
+++ b/code/modules/client/preferences/_preference.dm
@@ -405,7 +405,7 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key())
CRASH("`init_possible_values()` was not implemented for [type]!")
/// When `should_generate_icons` is TRUE, this proc is called for every value.
-/// It can return either an icon or a typepath to an atom to create.
+/// It can return either an /datum/universal_icon (see uni_icon() DEFINE) or a typepath to an atom to create.
/datum/preference/choiced/proc/icon_for(value)
SHOULD_CALL_PARENT(FALSE)
SHOULD_NOT_SLEEP(TRUE)
diff --git a/code/modules/client/preferences/ai_core_display.dm b/code/modules/client/preferences/ai_core_display.dm
index 924b475961e..c2eedc03e47 100644
--- a/code/modules/client/preferences/ai_core_display.dm
+++ b/code/modules/client/preferences/ai_core_display.dm
@@ -10,9 +10,9 @@
/datum/preference/choiced/ai_core_display/icon_for(value)
if (value == "Random")
- return icon('icons/mob/silicon/ai.dmi', "questionmark")
+ return uni_icon('icons/mob/silicon/ai.dmi', "questionmark")
else
- return icon('icons/mob/silicon/ai.dmi', resolve_ai_icon_sync(value))
+ return uni_icon('icons/mob/silicon/ai.dmi', resolve_ai_icon_sync(value))
/datum/preference/choiced/ai_core_display/is_accessible(datum/preferences/preferences)
if (!..(preferences))
diff --git a/code/modules/client/preferences/ai_emote_display.dm b/code/modules/client/preferences/ai_emote_display.dm
index eea059c8fe9..fc70e284583 100644
--- a/code/modules/client/preferences/ai_emote_display.dm
+++ b/code/modules/client/preferences/ai_emote_display.dm
@@ -10,9 +10,9 @@
/datum/preference/choiced/ai_emote_display/icon_for(value)
if (value == "Random")
- return icon('icons/mob/silicon/ai.dmi', "questionmark")
+ return uni_icon('icons/mob/silicon/ai.dmi', "questionmark")
else
- return icon('icons/obj/machines/status_display.dmi', GLOB.ai_status_display_emotes[value])
+ return uni_icon('icons/obj/machines/status_display.dmi', GLOB.ai_status_display_emotes[value])
/datum/preference/choiced/ai_emote_display/is_accessible(datum/preferences/preferences)
if (!..(preferences))
diff --git a/code/modules/client/preferences/ai_hologram_display.dm b/code/modules/client/preferences/ai_hologram_display.dm
index e71f806c3e4..f82803039cc 100644
--- a/code/modules/client/preferences/ai_hologram_display.dm
+++ b/code/modules/client/preferences/ai_hologram_display.dm
@@ -10,9 +10,9 @@
/datum/preference/choiced/ai_hologram_display/icon_for(value)
if (value == "Random")
- return icon('icons/mob/silicon/ai.dmi', "questionmark")
+ return uni_icon('icons/mob/silicon/ai.dmi', "questionmark")
else
- return icon(GLOB.ai_hologram_icons[value], GLOB.ai_hologram_icon_state[value])
+ return uni_icon(GLOB.ai_hologram_icons[value], GLOB.ai_hologram_icon_state[value])
/datum/preference/choiced/ai_hologram_display/is_accessible(datum/preferences/preferences)
if (!..(preferences))
diff --git a/code/modules/client/preferences/assets.dm b/code/modules/client/preferences/assets.dm
index a75178cc3e1..6659771323f 100644
--- a/code/modules/client/preferences/assets.dm
+++ b/code/modules/client/preferences/assets.dm
@@ -1,11 +1,9 @@
/// Assets generated from `/datum/preference` icons
-/datum/asset/spritesheet/preferences
+/datum/asset/spritesheet_batched/preferences
name = "preferences"
early = TRUE
-/datum/asset/spritesheet/preferences/create_spritesheets()
- var/list/to_insert = list()
-
+/datum/asset/spritesheet_batched/preferences/create_spritesheets()
for (var/preference_key in GLOB.preference_entries_by_key)
var/datum/preference/choiced/preference = GLOB.preference_entries_by_key[preference_key]
if (!istype(preference))
@@ -17,23 +15,20 @@
for (var/preference_value in preference.get_choices())
var/create_icon_of = preference.icon_for(preference_value)
- var/icon/icon
- var/icon_state
+ var/datum/universal_icon/icon
if (ispath(create_icon_of, /atom))
var/atom/atom_icon_source = create_icon_of
- icon = initial(atom_icon_source.icon)
- icon_state = initial(atom_icon_source.icon_state)
- else if (isicon(create_icon_of))
+ icon = get_display_icon_for(atom_icon_source)
+ else if (istype(create_icon_of, /datum/universal_icon))
icon = create_icon_of
+ else if (isicon(create_icon_of))
+ CRASH("Icon given for preference [preference_key]:[preference_value]. This is not supported anymore, provide a /datum/universal_icon instead.")
else
CRASH("[create_icon_of] is an invalid preference value (from [preference_key]:[preference_value]).")
-
- to_insert[preference.get_spritesheet_key(preference.serialize(preference_value))] = list(icon, icon_state)
-
- for (var/spritesheet_key in to_insert)
- var/list/inserting = to_insert[spritesheet_key]
- Insert(spritesheet_key, inserting[1], inserting[2])
+ // There's no cost associated with inserting uni_icons, so just insert them immediately.
+ var/spritesheet_key = preference.get_spritesheet_key(preference.serialize(preference_value))
+ insert_icon(spritesheet_key, icon)
/// Returns the key that will be used in the spritesheet for a given value.
/datum/preference/proc/get_spritesheet_key(value)
diff --git a/code/modules/client/preferences/clothing.dm b/code/modules/client/preferences/clothing.dm
index 074ed4e041a..50ede0ed393 100644
--- a/code/modules/client/preferences/clothing.dm
+++ b/code/modules/client/preferences/clothing.dm
@@ -1,14 +1,14 @@
-/proc/generate_underwear_icon(datum/sprite_accessory/accessory, icon/base_icon, color)
- var/icon/final_icon = new(base_icon)
+/proc/generate_underwear_icon(datum/sprite_accessory/accessory, datum/universal_icon/base_icon, color)
+ var/datum/universal_icon/final_icon = base_icon.copy()
if (!isnull(accessory))
- var/icon/accessory_icon = icon('icons/mob/clothing/underwear.dmi', accessory.icon_state)
+ var/datum/universal_icon/accessory_icon = uni_icon('icons/mob/clothing/underwear.dmi', accessory.icon_state)
if (color && !accessory.use_static)
- accessory_icon.Blend(color, ICON_MULTIPLY)
- final_icon.Blend(accessory_icon, ICON_OVERLAY)
+ accessory_icon.blend_color(color, ICON_MULTIPLY)
+ final_icon.blend_icon(accessory_icon, ICON_OVERLAY)
- final_icon.Crop(10, 1, 22, 13)
- final_icon.Scale(32, 32)
+ final_icon.crop(10, 1, 22, 13)
+ final_icon.scale(32, 32)
return final_icon
@@ -109,12 +109,12 @@
return /datum/sprite_accessory/socks/nude::name
/datum/preference/choiced/socks/icon_for(value)
- var/static/icon/lower_half
+ var/static/datum/universal_icon/lower_half
if (isnull(lower_half))
- lower_half = icon('icons/blanks/32x32.dmi', "nothing")
- lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg"), ICON_OVERLAY)
- lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY)
+ lower_half = uni_icon('icons/blanks/32x32.dmi', "nothing")
+ lower_half.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg"), ICON_OVERLAY)
+ lower_half.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY)
return generate_underwear_icon(SSaccessories.socks_list[value], lower_half)
@@ -147,24 +147,24 @@
return ..()
/datum/preference/choiced/undershirt/icon_for(value)
- var/static/icon/body
+ var/static/datum/universal_icon/body
if (isnull(body))
- body = icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg")
- body.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY)
- body.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_arm"), ICON_OVERLAY)
- body.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_arm"), ICON_OVERLAY)
- body.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_hand"), ICON_OVERLAY)
- body.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_hand"), ICON_OVERLAY)
- body.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_chest_m"), ICON_OVERLAY)
+ body = uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg")
+ body.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY)
+ body.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_arm"), ICON_OVERLAY)
+ body.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_arm"), ICON_OVERLAY)
+ body.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_hand"), ICON_OVERLAY)
+ body.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_hand"), ICON_OVERLAY)
+ body.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_chest_m"), ICON_OVERLAY)
- var/icon/icon_with_undershirt = icon(body)
+ var/datum/universal_icon/icon_with_undershirt = body.copy()
if (value != "Nude")
var/datum/sprite_accessory/accessory = SSaccessories.undershirt_list[value]
- icon_with_undershirt.Blend(icon('icons/mob/clothing/underwear.dmi', accessory.icon_state), ICON_OVERLAY)
+ icon_with_undershirt.blend_icon(uni_icon('icons/mob/clothing/underwear.dmi', accessory.icon_state), ICON_OVERLAY)
- icon_with_undershirt.Crop(9, 9, 23, 23)
- icon_with_undershirt.Scale(32, 32)
+ icon_with_undershirt.crop(9, 9, 23, 23)
+ icon_with_undershirt.scale(32, 32)
return icon_with_undershirt
/datum/preference/choiced/undershirt/apply_to_human(mob/living/carbon/human/target, value)
@@ -186,13 +186,13 @@
return /datum/sprite_accessory/underwear/male_hearts::name
/datum/preference/choiced/underwear/icon_for(value)
- var/static/icon/lower_half
+ var/static/datum/universal_icon/lower_half
if (isnull(lower_half))
- lower_half = icon('icons/blanks/32x32.dmi', "nothing")
- lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_chest_m"), ICON_OVERLAY)
- lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg"), ICON_OVERLAY)
- lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY)
+ lower_half = uni_icon('icons/blanks/32x32.dmi', "nothing")
+ lower_half.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_chest_m"), ICON_OVERLAY)
+ lower_half.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg"), ICON_OVERLAY)
+ lower_half.blend_icon(uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY)
return generate_underwear_icon(SSaccessories.underwear_list[value], lower_half, COLOR_ALMOST_BLACK)
diff --git a/code/modules/client/preferences/ghost.dm b/code/modules/client/preferences/ghost.dm
index e3d7fbf6328..3076fe650db 100644
--- a/code/modules/client/preferences/ghost.dm
+++ b/code/modules/client/preferences/ghost.dm
@@ -74,7 +74,7 @@
return assoc_to_keys(ghost_forms)
/datum/preference/choiced/ghost_form/icon_for(value)
- return icon('icons/mob/simple/mob.dmi', value)
+ return uni_icon('icons/mob/simple/mob.dmi', value)
/datum/preference/choiced/ghost_form/create_default_value()
return "ghost"
diff --git a/code/modules/client/preferences/glasses.dm b/code/modules/client/preferences/glasses.dm
index e5158f1acbb..5902ae9f9fe 100644
--- a/code/modules/client/preferences/glasses.dm
+++ b/code/modules/client/preferences/glasses.dm
@@ -12,9 +12,9 @@
/datum/preference/choiced/glasses/icon_for(value)
if (value == "Random")
- return icon('icons/effects/random_spawners.dmi', "questionmark")
+ return uni_icon('icons/effects/random_spawners.dmi', "questionmark")
else
- return icon('icons/obj/clothing/glasses.dmi', "glasses_[LOWER_TEXT(value)]")
+ return uni_icon('icons/obj/clothing/glasses.dmi', "glasses_[LOWER_TEXT(value)]")
/datum/preference/choiced/glasses/is_accessible(datum/preferences/preferences)
if (!..(preferences))
diff --git a/code/modules/client/preferences/middleware/species.dm b/code/modules/client/preferences/middleware/species.dm
index 9f1e4cf6c15..c7fc4265390 100644
--- a/code/modules/client/preferences/middleware/species.dm
+++ b/code/modules/client/preferences/middleware/species.dm
@@ -23,7 +23,7 @@
var/icon/dummy_icon = getFlatIcon(dummy)
dummy_icon.Scale(64, 64)
- dummy_icon.Crop(15, 64, 15 + 31, 64 - 31)
+ dummy_icon.Crop(15, 64 - 31, 15 + 31, 64)
dummy_icon.Scale(64, 64)
to_insert[sanitize_css_class_name(initial(species_type.name))] = dummy_icon
diff --git a/code/modules/client/preferences/species_features/basic.dm b/code/modules/client/preferences/species_features/basic.dm
index e2802b3d29d..260848d8511 100644
--- a/code/modules/client/preferences/species_features/basic.dm
+++ b/code/modules/client/preferences/species_features/basic.dm
@@ -1,21 +1,21 @@
/proc/generate_icon_with_head_accessory(datum/sprite_accessory/sprite_accessory, y_offset = 0)
- var/static/icon/head_icon
+ var/static/datum/universal_icon/head_icon
if (isnull(head_icon))
- head_icon = icon('icons/mob/human/bodyparts_greyscale.dmi', "human_head_m")
- head_icon.Blend(skintone2hex("caucasian1"), ICON_MULTIPLY)
+ head_icon = uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "human_head_m")
+ head_icon.blend_color(skintone2hex("caucasian1"), ICON_MULTIPLY)
- var/icon/final_icon = new(head_icon)
- if (!isnull(sprite_accessory))
+ var/datum/universal_icon/final_icon = head_icon.copy()
+ if (!isnull(sprite_accessory) && sprite_accessory.icon_state != SPRITE_ACCESSORY_NONE)
ASSERT(istype(sprite_accessory))
- var/icon/head_accessory_icon = icon(sprite_accessory.icon, sprite_accessory.icon_state)
+ var/datum/universal_icon/head_accessory_icon = uni_icon(sprite_accessory.icon, sprite_accessory.icon_state)
if(y_offset)
- head_accessory_icon.Shift(NORTH, y_offset)
- head_accessory_icon.Blend(COLOR_DARK_BROWN, ICON_MULTIPLY)
- final_icon.Blend(head_accessory_icon, ICON_OVERLAY)
+ head_accessory_icon.shift(NORTH, y_offset, ICON_SIZE_X, ICON_SIZE_Y)
+ head_accessory_icon.blend_color(COLOR_DARK_BROWN, ICON_MULTIPLY)
+ final_icon.blend_icon(head_accessory_icon, ICON_OVERLAY)
- final_icon.Crop(10, 19, 22, 31)
- final_icon.Scale(32, 32)
+ final_icon.crop(10, 19, 22, 31)
+ final_icon.scale(32, 32)
return final_icon
diff --git a/code/modules/client/preferences/species_features/ethereal.dm b/code/modules/client/preferences/species_features/ethereal.dm
index 19c2fd650e1..60117d3cd98 100644
--- a/code/modules/client/preferences/species_features/ethereal.dm
+++ b/code/modules/client/preferences/species_features/ethereal.dm
@@ -9,22 +9,23 @@
return assoc_to_keys(GLOB.color_list_ethereal)
/datum/preference/choiced/ethereal_color/icon_for(value)
- var/static/icon/ethereal_base
+ var/static/datum/universal_icon/ethereal_base
if (isnull(ethereal_base))
- ethereal_base = icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_head")
- ethereal_base.Blend(icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_chest"), ICON_OVERLAY)
- ethereal_base.Blend(icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_l_arm"), ICON_OVERLAY)
- ethereal_base.Blend(icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_r_arm"), ICON_OVERLAY)
+ ethereal_base = uni_icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_head")
+ ethereal_base.blend_icon(uni_icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_chest"), ICON_OVERLAY)
+ ethereal_base.blend_icon(uni_icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_l_arm"), ICON_OVERLAY)
+ ethereal_base.blend_icon(uni_icon('icons/mob/human/species/ethereal/bodyparts.dmi', "ethereal_r_arm"), ICON_OVERLAY)
- var/icon/eyes = icon('icons/mob/human/human_face.dmi', "eyes")
- eyes.Blend(COLOR_BLACK, ICON_MULTIPLY)
- ethereal_base.Blend(eyes, ICON_OVERLAY)
+ var/datum/universal_icon/eyes = uni_icon('icons/mob/human/human_face.dmi', "eyes_l")
+ eyes.blend_icon(uni_icon('icons/mob/human/human_face.dmi', "eyes_r"), ICON_OVERLAY)
+ eyes.blend_color(COLOR_BLACK, ICON_MULTIPLY)
+ ethereal_base.blend_icon(eyes, ICON_OVERLAY)
- ethereal_base.Scale(64, 64)
- ethereal_base.Crop(15, 64, 15 + 31, 64 - 31)
+ ethereal_base.scale(64, 64)
+ ethereal_base.crop(15, 64 - 31, 15 + 31, 64)
- var/icon/icon = new(ethereal_base)
- icon.Blend(GLOB.color_list_ethereal[value], ICON_MULTIPLY)
+ var/datum/universal_icon/icon = ethereal_base.copy()
+ icon.blend_color(GLOB.color_list_ethereal[value], ICON_MULTIPLY)
return icon
/datum/preference/choiced/ethereal_color/apply_to_human(mob/living/carbon/human/target, value)
diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm
index 3487edb78fd..f1e3e43660a 100644
--- a/code/modules/client/preferences/species_features/lizard.dm
+++ b/code/modules/client/preferences/species_features/lizard.dm
@@ -1,25 +1,25 @@
/proc/generate_lizard_side_shot(datum/sprite_accessory/sprite_accessory, key, include_snout = TRUE)
- var/static/icon/lizard
- var/static/icon/lizard_with_snout
+ var/static/datum/universal_icon/lizard
+ var/static/datum/universal_icon/lizard_with_snout
if (isnull(lizard))
- lizard = icon('icons/mob/human/species/lizard/bodyparts.dmi', "lizard_head", EAST)
- var/icon/eyes = icon('icons/mob/human/human_face.dmi', "eyes", EAST)
- eyes.Blend(COLOR_GRAY, ICON_MULTIPLY)
- lizard.Blend(eyes, ICON_OVERLAY)
+ lizard = uni_icon('icons/mob/human/species/lizard/bodyparts.dmi', "lizard_head", EAST)
+ var/datum/universal_icon/eyes = uni_icon('icons/mob/human/human_face.dmi', "eyes_l", EAST)
+ eyes.blend_color(COLOR_GRAY, ICON_MULTIPLY)
+ lizard.blend_icon(eyes, ICON_OVERLAY)
- lizard_with_snout = icon(lizard)
- lizard_with_snout.Blend(icon('icons/mob/human/species/lizard/lizard_misc.dmi', "m_snout_round_ADJ", EAST), ICON_OVERLAY)
+ lizard_with_snout = lizard.copy()
+ lizard_with_snout.blend_icon(uni_icon('icons/mob/human/species/lizard/lizard_misc.dmi', "m_snout_round_ADJ", EAST), ICON_OVERLAY)
- var/icon/final_icon = include_snout ? icon(lizard_with_snout) : icon(lizard)
+ var/datum/universal_icon/final_icon = include_snout ? lizard_with_snout.copy() : lizard.copy()
- if (!isnull(sprite_accessory))
- var/icon/accessory_icon = icon(sprite_accessory.icon, "m_[key]_[sprite_accessory.icon_state]_ADJ", EAST)
- final_icon.Blend(accessory_icon, ICON_OVERLAY)
+ if (!isnull(sprite_accessory) && sprite_accessory.icon_state != SPRITE_ACCESSORY_NONE)
+ var/datum/universal_icon/accessory_icon = uni_icon(sprite_accessory.icon, "m_[key]_[sprite_accessory.icon_state]_ADJ", EAST)
+ final_icon.blend_icon(accessory_icon, ICON_OVERLAY)
- final_icon.Crop(11, 20, 23, 32)
- final_icon.Scale(32, 32)
- final_icon.Blend(COLOR_VIBRANT_LIME, ICON_MULTIPLY)
+ final_icon.crop(11, 20, 23, 32)
+ final_icon.scale(32, 32)
+ final_icon.blend_color(COLOR_VIBRANT_LIME, ICON_MULTIPLY)
return final_icon
@@ -37,20 +37,20 @@
/datum/preference/choiced/lizard_body_markings/icon_for(value)
var/datum/sprite_accessory/sprite_accessory = SSaccessories.lizard_markings_list[value]
- var/icon/final_icon = icon('icons/mob/human/species/lizard/bodyparts.dmi', "lizard_chest_m")
+ var/datum/universal_icon/final_icon = uni_icon('icons/mob/human/species/lizard/bodyparts.dmi', "lizard_chest_m")
- if (sprite_accessory.icon_state != "none")
- var/icon/body_markings_icon = icon(
- 'icons/mob/human/species/lizard/lizard_misc.dmi',
+ if (sprite_accessory.icon_state != SPRITE_ACCESSORY_NONE)
+ var/datum/universal_icon/body_markings_icon = uni_icon(
+ sprite_accessory.icon,
"male_[sprite_accessory.icon_state]_chest",
)
- final_icon.Blend(body_markings_icon, ICON_OVERLAY)
+ final_icon.blend_icon(body_markings_icon, ICON_OVERLAY)
- final_icon.Blend(COLOR_VIBRANT_LIME, ICON_MULTIPLY)
- final_icon.Crop(10, 8, 22, 23)
- final_icon.Scale(26, 32)
- final_icon.Crop(-2, 1, 29, 32)
+ final_icon.blend_color(COLOR_VIBRANT_LIME, ICON_MULTIPLY)
+ final_icon.crop(10, 8, 22, 23)
+ final_icon.scale(26, 32)
+ final_icon.crop(-2, 1, 29, 32)
return final_icon
diff --git a/code/modules/client/preferences/species_features/moth.dm b/code/modules/client/preferences/species_features/moth.dm
index f697d857d4f..7a9031b98ba 100644
--- a/code/modules/client/preferences/species_features/moth.dm
+++ b/code/modules/client/preferences/species_features/moth.dm
@@ -9,19 +9,19 @@
return assoc_to_keys_features(SSaccessories.moth_antennae_list)
/datum/preference/choiced/moth_antennae/icon_for(value)
- var/static/icon/moth_head
+ var/static/datum/universal_icon/moth_head
if (isnull(moth_head))
- moth_head = icon('icons/mob/human/species/moth/bodyparts.dmi', "moth_head")
- moth_head.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_l"), ICON_OVERLAY)
- moth_head.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_r"), ICON_OVERLAY)
+ moth_head = uni_icon('icons/mob/human/species/moth/bodyparts.dmi', "moth_head")
+ moth_head.blend_icon(uni_icon('icons/mob/human/human_face.dmi', "motheyes_l"), ICON_OVERLAY)
+ moth_head.blend_icon(uni_icon('icons/mob/human/human_face.dmi', "motheyes_r"), ICON_OVERLAY)
var/datum/sprite_accessory/antennae = SSaccessories.moth_antennae_list[value]
- var/icon/icon_with_antennae = new(moth_head)
- icon_with_antennae.Blend(icon(antennae.icon, "m_moth_antennae_[antennae.icon_state]_FRONT"), ICON_OVERLAY)
- icon_with_antennae.Scale(64, 64)
- icon_with_antennae.Crop(15, 64, 15 + 31, 64 - 31)
+ var/datum/universal_icon/icon_with_antennae = moth_head.copy()
+ icon_with_antennae.blend_icon(uni_icon(antennae.icon, "m_moth_antennae_[antennae.icon_state]_FRONT"), ICON_OVERLAY)
+ icon_with_antennae.scale(64, 64)
+ icon_with_antennae.crop(15, 64 - 31, 15 + 31, 64)
return icon_with_antennae
@@ -47,33 +47,31 @@
/obj/item/bodypart/arm/right/moth,
)
- var/static/icon/moth_body
+ var/static/datum/universal_icon/moth_body
if (isnull(moth_body))
- moth_body = icon('icons/blanks/32x32.dmi', "nothing")
-
- moth_body.Blend(icon('icons/mob/human/species/moth/moth_wings.dmi', "m_moth_wings_plain_BEHIND"), ICON_OVERLAY)
+ moth_body = uni_icon('icons/blanks/32x32.dmi', "nothing")
for (var/obj/item/bodypart/body_part as anything in body_parts)
- moth_body.Blend(icon('icons/mob/human/species/moth/bodyparts.dmi', initial(body_part.icon_state)), ICON_OVERLAY)
+ moth_body.blend_icon(uni_icon('icons/mob/human/species/moth/bodyparts.dmi', initial(body_part.icon_state)), ICON_OVERLAY)
- moth_body.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_l"), ICON_OVERLAY)
- moth_body.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_r"), ICON_OVERLAY)
+ moth_body.blend_icon(uni_icon('icons/mob/human/human_face.dmi', "motheyes_l"), ICON_OVERLAY)
+ moth_body.blend_icon(uni_icon('icons/mob/human/human_face.dmi', "motheyes_r"), ICON_OVERLAY)
var/datum/sprite_accessory/markings = SSaccessories.moth_markings_list[value]
- var/icon/icon_with_markings = new(moth_body)
+ var/datum/universal_icon/icon_with_markings = moth_body.copy()
- if (value != "None")
+ if (value != SPRITE_ACCESSORY_NONE)
for (var/obj/item/bodypart/body_part as anything in body_parts)
- var/icon/body_part_icon = icon(markings.icon, "[markings.icon_state]_[initial(body_part.body_zone)]")
- body_part_icon.Crop(1, 1, 32, 32)
- icon_with_markings.Blend(body_part_icon, ICON_OVERLAY)
+ var/datum/universal_icon/body_part_icon = uni_icon(markings.icon, "[markings.icon_state]_[initial(body_part.body_zone)]")
+ body_part_icon.crop(1, 1, 32, 32)
+ icon_with_markings.blend_icon(body_part_icon, ICON_OVERLAY)
- icon_with_markings.Blend(icon('icons/mob/human/species/moth/moth_wings.dmi', "m_moth_wings_plain_FRONT"), ICON_OVERLAY)
- icon_with_markings.Blend(icon('icons/mob/human/species/moth/moth_antennae.dmi', "m_moth_antennae_plain_FRONT"), ICON_OVERLAY)
+ icon_with_markings.blend_icon(uni_icon('icons/mob/human/species/moth/moth_wings.dmi', "m_moth_wings_plain_FRONT"), ICON_OVERLAY)
+ icon_with_markings.blend_icon(uni_icon('icons/mob/human/species/moth/moth_antennae.dmi', "m_moth_antennae_plain_FRONT"), ICON_OVERLAY)
// Zoom in on the top of the head and the chest
- icon_with_markings.Scale(64, 64)
- icon_with_markings.Crop(15, 64, 15 + 31, 64 - 31)
+ icon_with_markings.scale(64, 64)
+ icon_with_markings.crop(15, 64 - 31, 15 + 31, 64)
return icon_with_markings
@@ -92,9 +90,7 @@
/datum/preference/choiced/moth_wings/icon_for(value)
var/datum/sprite_accessory/moth_wings = SSaccessories.moth_wings_list[value]
- var/icon/final_icon = icon(moth_wings.icon, "m_moth_wings_[moth_wings.icon_state]_BEHIND")
- final_icon.Blend(icon(moth_wings.icon, "m_moth_wings_[moth_wings.icon_state]_FRONT"), ICON_OVERLAY)
- return final_icon
+ return uni_icon(moth_wings.icon, "m_moth_wings_[moth_wings.icon_state]_BEHIND")
/datum/preference/choiced/moth_wings/apply_to_human(mob/living/carbon/human/target, value)
target.dna.features["moth_wings"] = value
diff --git a/code/modules/client/preferences/species_features/pod.dm b/code/modules/client/preferences/species_features/pod.dm
index de3d5221f7a..733cfb568f2 100644
--- a/code/modules/client/preferences/species_features/pod.dm
+++ b/code/modules/client/preferences/species_features/pod.dm
@@ -11,15 +11,15 @@
/datum/preference/choiced/pod_hair/icon_for(value)
var/datum/sprite_accessory/pod_hair = SSaccessories.pod_hair_list[value]
- var/icon/icon_with_hair = icon('icons/mob/human/bodyparts_greyscale.dmi', "pod_head_m")
+ var/datum/universal_icon/icon_with_hair = uni_icon('icons/mob/human/bodyparts_greyscale.dmi', "pod_head_m")
- var/icon/icon_adj = icon(pod_hair.icon, "m_pod_hair_[pod_hair.icon_state]_ADJ")
- var/icon/icon_front = icon(pod_hair.icon, "m_pod_hair_[pod_hair.icon_state]_FRONT")
- icon_adj.Blend(icon_front, ICON_OVERLAY)
- icon_with_hair.Blend(icon_adj, ICON_OVERLAY)
- icon_with_hair.Scale(64, 64)
- icon_with_hair.Crop(15, 64, 15 + 31, 64 - 31)
- icon_with_hair.Blend(COLOR_GREEN, ICON_MULTIPLY)
+ var/datum/universal_icon/icon_adj = uni_icon(pod_hair.icon, "m_pod_hair_[pod_hair.icon_state]_ADJ")
+ var/datum/universal_icon/icon_front = uni_icon(pod_hair.icon, "m_pod_hair_[pod_hair.icon_state]_FRONT")
+ icon_adj.blend_icon(icon_front, ICON_OVERLAY)
+ icon_with_hair.blend_icon(icon_adj, ICON_OVERLAY)
+ icon_with_hair.scale(64, 64)
+ icon_with_hair.crop(15, 64 - 31, 15 + 31, 64)
+ icon_with_hair.blend_color(COLOR_GREEN, ICON_MULTIPLY)
return icon_with_hair
diff --git a/code/modules/client/preferences/species_features/vampire.dm b/code/modules/client/preferences/species_features/vampire.dm
index f720dca3649..cc54fb3391f 100644
--- a/code/modules/client/preferences/species_features/vampire.dm
+++ b/code/modules/client/preferences/species_features/vampire.dm
@@ -16,9 +16,9 @@
/datum/preference/choiced/vampire_status/icon_for(value)
switch (value)
if ("Inoculated")
- return icon('icons/obj/drinks/drinks.dmi', "bloodglass")
+ return uni_icon('icons/obj/drinks/drinks.dmi', "bloodglass")
if ("Outcast")
- return icon('icons/obj/medical/bloodpack.dmi', "generic_bloodpack")
+ return uni_icon('icons/obj/medical/bloodpack.dmi', "generic_bloodpack")
///list that stores a vampire house name for each department
GLOBAL_LIST_EMPTY(vampire_houses)
diff --git a/code/modules/client/preferences/ui_style.dm b/code/modules/client/preferences/ui_style.dm
index fa002fd71b0..77aacdc80ff 100644
--- a/code/modules/client/preferences/ui_style.dm
+++ b/code/modules/client/preferences/ui_style.dm
@@ -9,11 +9,11 @@
return assoc_to_keys(GLOB.available_ui_styles)
/datum/preference/choiced/ui_style/icon_for(value)
- var/icon/icons = GLOB.available_ui_styles[value]
+ var/ui_icons = GLOB.available_ui_styles[value]
- var/icon/icon = icon(icons, "hand_r")
- icon.Crop(1, 1, ICON_SIZE_X * 2, ICON_SIZE_Y)
- icon.Blend(icon(icons, "hand_l"), ICON_OVERLAY, ICON_SIZE_X)
+ var/datum/universal_icon/icon = uni_icon(ui_icons, "hand_l")
+ icon.crop(1 - ICON_SIZE_X, 1, ICON_SIZE_Y, ICON_SIZE_X)
+ icon.blend_icon(uni_icon(ui_icons, "hand_r"), ICON_OVERLAY)
return icon
diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm
index 0f49164a106..3fc6b0fa70c 100644
--- a/code/modules/client/verbs/ooc.dm
+++ b/code/modules/client/verbs/ooc.dm
@@ -82,7 +82,7 @@ GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8")
if(prefs.toggles & MEMBER_PUBLIC)
keyname = "[icon2html('icons/ui/chat/member_content.dmi', world, "blag")][keyname]"
if(prefs.hearted)
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
+ var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet_batched/chat)
keyname = "[sheet.icon_tag("emoji-heart")][keyname]"
//The linkify span classes and linkify=TRUE below make ooc text get clickable chat href links if you pass in something resembling a url
for(var/client/receiver as anything in GLOB.clients)
diff --git a/code/modules/emoji/emoji_parse.dm b/code/modules/emoji/emoji_parse.dm
index fa9b9ea0d5e..35374e1c69a 100644
--- a/code/modules/emoji/emoji_parse.dm
+++ b/code/modules/emoji/emoji_parse.dm
@@ -17,7 +17,7 @@
search = findtext(text, ":", pos + length(text[pos]))
if(search)
emoji = LOWER_TEXT(copytext(text, pos + length(text[pos]), search))
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
+ var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet_batched/chat)
var/tag = sheet.icon_tag("emoji-[emoji]")
if(tag)
parsed += tag
diff --git a/code/modules/fishing/fish_catalog.dm b/code/modules/fishing/fish_catalog.dm
index f95358c8763..dd7a03cc53b 100644
--- a/code/modules/fishing/fish_catalog.dm
+++ b/code/modules/fishing/fish_catalog.dm
@@ -111,5 +111,5 @@
/obj/item/book/manual/fish_catalog/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/fish)
+ get_asset_datum(/datum/asset/spritesheet_batched/fish)
)
diff --git a/code/modules/hydroponics/biogenerator.dm b/code/modules/hydroponics/biogenerator.dm
index f5e7c7b29a4..b15a835b101 100644
--- a/code/modules/hydroponics/biogenerator.dm
+++ b/code/modules/hydroponics/biogenerator.dm
@@ -465,7 +465,7 @@
/obj/machinery/biogenerator/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/research_designs),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs),
)
diff --git a/code/modules/hydroponics/seed_extractor.dm b/code/modules/hydroponics/seed_extractor.dm
index c8b890a2959..15362dbf14d 100644
--- a/code/modules/hydroponics/seed_extractor.dm
+++ b/code/modules/hydroponics/seed_extractor.dm
@@ -311,5 +311,5 @@
/obj/machinery/seed_extractor/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/seeds)
+ get_asset_datum(/datum/asset/spritesheet_batched/seeds)
)
diff --git a/code/modules/language/_language.dm b/code/modules/language/_language.dm
index 1177af83467..eda32fc68e3 100644
--- a/code/modules/language/_language.dm
+++ b/code/modules/language/_language.dm
@@ -56,7 +56,7 @@
/// Returns the icon to display in the chat window when speaking this language.
/datum/language/proc/get_icon()
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat)
+ var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet_batched/chat)
return sheet.icon_tag("language-[icon_state]")
/// Simple helper for getting a default firstname lastname
diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm
index c3adc298b63..4806f9cb690 100644
--- a/code/modules/library/lib_machines.dm
+++ b/code/modules/library/lib_machines.dm
@@ -406,7 +406,7 @@ GLOBAL_VAR_INIT(library_table_modified, 0)
return data
/obj/machinery/computer/libraryconsole/bookmanagement/ui_assets(mob/user)
- return list(get_asset_datum(/datum/asset/spritesheet/bibles))
+ return list(get_asset_datum(/datum/asset/spritesheet_batched/bibles))
/obj/machinery/computer/libraryconsole/bookmanagement/proc/load_nearby_books()
for(var/datum/book_info/book as anything in SSlibrary.get_area_books(get_area(src)))
diff --git a/code/modules/mafia/controller_ui.dm b/code/modules/mafia/controller_ui.dm
index 578555d21f4..f45ea3a80aa 100644
--- a/code/modules/mafia/controller_ui.dm
+++ b/code/modules/mafia/controller_ui.dm
@@ -77,7 +77,7 @@
/datum/mafia_controller/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/mafia),
+ get_asset_datum(/datum/asset/spritesheet_batched/mafia),
)
/datum/mafia_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm
index 7fe2e9468f1..9756935b96c 100644
--- a/code/modules/mining/machine_silo.dm
+++ b/code/modules/mining/machine_silo.dm
@@ -98,7 +98,7 @@
/obj/machinery/ore_silo/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials)
)
/obj/machinery/ore_silo/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/modules/modular_computers/file_system/programs/emojipedia.dm b/code/modules/modular_computers/file_system/programs/emojipedia.dm
index 10be69cc349..84daee76cec 100644
--- a/code/modules/modular_computers/file_system/programs/emojipedia.dm
+++ b/code/modules/modular_computers/file_system/programs/emojipedia.dm
@@ -27,5 +27,5 @@
/datum/computer_file/program/emojipedia/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/emojipedia),
+ get_asset_datum(/datum/asset/spritesheet_batched/emojipedia),
)
diff --git a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
index 4490e05a46b..259c280a668 100644
--- a/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
+++ b/code/modules/modular_computers/file_system/programs/messenger/messenger_program.dm
@@ -385,7 +385,7 @@
/datum/computer_file/program/messenger/ui_assets(mob/user)
. = ..()
- . += get_asset_datum(/datum/asset/spritesheet/chat)
+ . += get_asset_datum(/datum/asset/spritesheet_batched/chat)
//////////////////////
// MESSAGE HANDLING //
diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm
index 4e181370fe2..2c32a65d1fe 100644
--- a/code/modules/modular_computers/file_system/programs/techweb.dm
+++ b/code/modules/modular_computers/file_system/programs/techweb.dm
@@ -39,7 +39,7 @@
/datum/computer_file/program/science/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/research_designs)
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
// heavy data from this proc should be moved to static data when possible
@@ -167,7 +167,7 @@
// Build design cache
var/design_cache = list()
- var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
+ var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
for (var/design_id in SSresearch.techweb_designs)
var/datum/design/design = SSresearch.techweb_designs[design_id] || SSresearch.error_design
diff --git a/code/modules/paperwork/stamps.dm b/code/modules/paperwork/stamps.dm
index 1eae74d242c..1a7e3ac0b1b 100644
--- a/code/modules/paperwork/stamps.dm
+++ b/code/modules/paperwork/stamps.dm
@@ -20,7 +20,7 @@
return OXYLOSS
/obj/item/stamp/get_writing_implement_details()
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/simple/paper)
+ var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet/simple/paper)
return list(
interaction_mode = MODE_STAMPING,
stamp_icon_state = icon_state,
diff --git a/code/modules/plumbing/plumbers/pill_press.dm b/code/modules/plumbing/plumbers/pill_press.dm
index 445eae02f27..f76c72977ee 100644
--- a/code/modules/plumbing/plumbers/pill_press.dm
+++ b/code/modules/plumbing/plumbers/pill_press.dm
@@ -28,7 +28,7 @@
. = ..()
if(!packaging_types)
- var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/chemmaster)
+ var/datum/asset/spritesheet_batched/assets = get_asset_datum(/datum/asset/spritesheet_batched/chemmaster)
var/list/types = list(
CAT_PILLS = GLOB.reagent_containers[CAT_PILLS],
@@ -100,7 +100,7 @@
/obj/machinery/plumbing/pill_press/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/chemmaster)
+ get_asset_datum(/datum/asset/spritesheet_batched/chemmaster)
)
/obj/machinery/plumbing/pill_press/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm
index 643c710f6b8..fd0ed96de61 100644
--- a/code/modules/research/designs.dm
+++ b/code/modules/research/designs.dm
@@ -78,7 +78,7 @@ other types of metals and chemistry for reagents).
materials = temp_list
/datum/design/proc/icon_html(client/user)
- var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
+ var/datum/asset/spritesheet_batched/sheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
sheet.send(user)
return sheet.icon_tag(id)
diff --git a/code/modules/research/machinery/_production.dm b/code/modules/research/machinery/_production.dm
index c9988977fe4..5fd5f3fcd35 100644
--- a/code/modules/research/machinery/_production.dm
+++ b/code/modules/research/machinery/_production.dm
@@ -224,8 +224,8 @@
/obj/machinery/rnd/production/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
- get_asset_datum(/datum/asset/spritesheet/research_designs)
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/rnd/production/ui_interact(mob/user, datum/tgui/ui)
@@ -239,7 +239,7 @@
var/list/designs = list()
- var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
+ var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
var/coefficient
diff --git a/code/modules/research/rdconsole.dm b/code/modules/research/rdconsole.dm
index 517eb7c5b9e..000dd813c37 100644
--- a/code/modules/research/rdconsole.dm
+++ b/code/modules/research/rdconsole.dm
@@ -176,7 +176,7 @@ Nothing else in the console has ID requirements.
/obj/machinery/computer/rdconsole/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/research_designs),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs),
)
// heavy data from this proc should be moved to static data when possible
@@ -300,7 +300,7 @@ Nothing else in the console has ID requirements.
// Build design cache
var/design_cache = list()
- var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
+ var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
for (var/design_id in SSresearch.techweb_designs)
var/datum/design/design = SSresearch.techweb_designs[design_id] || SSresearch.error_design
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
index 82d8d794d9a..4c0fcf6c4a0 100644
--- a/code/modules/tgui/tgui_window.dm
+++ b/code/modules/tgui/tgui_window.dm
@@ -313,6 +313,9 @@
if(istype(asset, /datum/asset/spritesheet))
var/datum/asset/spritesheet/spritesheet = asset
send_message("asset/stylesheet", spritesheet.css_filename())
+ else if(istype(asset, /datum/asset/spritesheet_batched))
+ var/datum/asset/spritesheet_batched/spritesheet = asset
+ send_message("asset/stylesheet", spritesheet.css_filename())
send_raw_message(asset.get_serialized_url_mappings())
/**
diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm
index a54f56ead0c..658bdb19c54 100644
--- a/code/modules/tgui_panel/tgui_panel.dm
+++ b/code/modules/tgui_panel/tgui_panel.dm
@@ -49,7 +49,7 @@
))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/tgfont))
- window.send_asset(get_asset_datum(/datum/asset/spritesheet/chat))
+ window.send_asset(get_asset_datum(/datum/asset/spritesheet_batched/chat))
// Other setup
request_telemetry()
addtimer(CALLBACK(src, PROC_REF(on_initialize_timed_out)), 5 SECONDS)
diff --git a/code/modules/transport/tram/tram_doors.dm b/code/modules/transport/tram/tram_doors.dm
index 5eb3be234e3..0462fae7c53 100644
--- a/code/modules/transport/tram/tram_doors.dm
+++ b/code/modules/transport/tram/tram_doors.dm
@@ -10,6 +10,7 @@
multi_tile = TRUE
opacity = FALSE
assemblytype = /obj/structure/door_assembly/multi_tile/door_assembly_tram
+ can_be_glass = FALSE
airlock_material = "glass"
air_tight = TRUE
req_access = list(ACCESS_TCOMMS)
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index 465f744cb64..12200a7cce5 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -96,6 +96,7 @@
#include "antag_moodlets.dm"
#include "area_contents.dm"
#include "armor_verification.dm"
+#include "asset_smart_cache.dm"
#include "atmospherics_sanity.dm"
#include "autowiki.dm"
#include "bake_a_cake.dm"
diff --git a/code/modules/unit_tests/asset_smart_cache.dm b/code/modules/unit_tests/asset_smart_cache.dm
new file mode 100644
index 00000000000..297951376a3
--- /dev/null
+++ b/code/modules/unit_tests/asset_smart_cache.dm
@@ -0,0 +1,65 @@
+/datum/asset/spritesheet_batched/test
+ name = "test"
+ load_immediately = TRUE
+ force_cache = TRUE
+ // Don't let the asset subsystem load this. This is how we trick it.
+ _abstract = /datum/asset/spritesheet_batched/test
+ var/static/list/items = list(/obj/item/binoculars, /obj/item/camera, /obj/item/clothing/under/color/blue, /obj/item/clothing/under/color/black)
+
+/datum/asset/spritesheet_batched/test/create_spritesheets()
+ for(var/atom/item as anything in items)
+ if (!ispath(item, /atom))
+ return FALSE
+ var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
+ insert_icon(imgid, get_display_icon_for(item))
+ // Get some coverage on each operation.
+ var/datum/universal_icon/I = uni_icon('icons/effects/effects.dmi', "nothing")
+ I.blend_icon(uni_icon('icons/effects/effects.dmi', "sparks"), ICON_OVERLAY)
+ I.blend_color("#ff0000", ICON_MULTIPLY)
+ I.scale(64, 64)
+ I.crop(1, 1, 128, 64) // we'll test for the scale later.
+ insert_icon("test", I)
+
+/datum/asset/spritesheet_batched/test/unregister()
+ SSassets.transport.unregister_asset("spritesheet_[name].css")
+ if(length(sizes))
+ for(var/size_id in sizes)
+ SSassets.transport.unregister_asset("[name]_[size_id].png")
+
+/datum/unit_test/test_asset_smart_cache/Run()
+ fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json")
+ fdel("data/spritesheets/spritesheet_test.css")
+ var/datum/asset/spritesheet_batched/test/sheet = new()
+ TEST_ASSERT(sheet.fully_generated, "Spritesheet not generated!")
+ // Cache should be invalid initially.
+ TEST_ASSERT(sheet.cache_result, "Spritesheet smart cache was VALID when it should be INVALID!")
+ for(var/item in sheet.items)
+ var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
+ // All items should be in sprites list.
+ TEST_ASSERT(imgid in sheet.sprites, "Item [item] not present in spritesheet result!")
+ TEST_ASSERT("test" in sheet.sprites, "Item test not present in spritesheet result!")
+ TEST_ASSERT("128x64" in sheet.sizes, "Test icon was not output as 128x64!")
+ // cache wrote properly
+ TEST_ASSERT(fexists("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json"), "Smart cache entry did not write!")
+ // Clear it out and get ready to do it again, this time loading from cache
+ sheet.unregister()
+ sheet.entries = list()
+ sheet.sprites = list()
+ sheet.sizes = list()
+ sheet.job_id = null
+ sheet.cache_result = null
+ sheet.cache_data = null
+ sheet.cache_job_id = null
+ sheet.fully_generated = FALSE
+
+ sheet.register()
+ TEST_ASSERT(sheet.fully_generated, "Spritesheet did not load from smart cache properly!")
+ // Check for CACHE_VALID
+ TEST_ASSERT(!sheet.cache_result, "Spritesheet did not load from smart cache, it was invalid despite having the same input data!")
+ // Cleanup files.
+ fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json")
+ fdel("data/spritesheets/spritesheet_test.css")
+ for(var/size in sheet.sizes)
+ fdel("data/spritesheets/test_[size].png")
+
+
diff --git a/code/modules/unit_tests/preferences.dm b/code/modules/unit_tests/preferences.dm
index 626d5b48466..32a8542be2a 100644
--- a/code/modules/unit_tests/preferences.dm
+++ b/code/modules/unit_tests/preferences.dm
@@ -65,7 +65,7 @@
if (choiced_preference.should_generate_icons)
for (var/value in values)
var/icon = choiced_preference.icon_for(value)
- TEST_ASSERT(istype(icon, /icon) || ispath(icon), "[preference_type] gave [icon] as an icon for [value], which is not a valid value")
+ TEST_ASSERT(istype(icon, /datum/universal_icon) || ispath(icon), "[preference_type] gave [icon] as an icon for [value], which is not a valid value")
else
var/errored = FALSE
diff --git a/code/modules/unit_tests/spritesheets.dm b/code/modules/unit_tests/spritesheets.dm
index c7c16c6535e..16666dac17f 100644
--- a/code/modules/unit_tests/spritesheets.dm
+++ b/code/modules/unit_tests/spritesheets.dm
@@ -5,6 +5,19 @@
for(var/datum/asset/spritesheet/sheet as anything in subtypesof(/datum/asset/spritesheet))
if(!initial(sheet.name)) //Ignore abstract types
continue
+ if (sheet == initial(sheet._abstract))
+ continue
+ sheet = get_asset_datum(sheet)
+ for(var/sprite_name in sheet.sprites)
+ if(!sprite_name)
+ TEST_FAIL("Spritesheet [sheet.type] has a nameless icon state.")
+
+ // Test IconForge generated sheets as well
+ for(var/datum/asset/spritesheet_batched/sheet as anything in subtypesof(/datum/asset/spritesheet_batched))
+ if(!initial(sheet.name)) //Ignore abstract types
+ continue
+ if (sheet == initial(sheet._abstract))
+ continue
sheet = get_asset_datum(sheet)
for(var/sprite_name in sheet.sprites)
if(!sprite_name)
diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm
index 7667178392a..d66a573a2e8 100644
--- a/code/modules/vehicles/mecha/mech_fabricator.dm
+++ b/code/modules/vehicles/mecha/mech_fabricator.dm
@@ -367,8 +367,8 @@
/obj/machinery/mecha_part_fabricator/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
- get_asset_datum(/datum/asset/spritesheet/research_designs)
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/mecha_part_fabricator/ui_interact(mob/user, datum/tgui/ui)
@@ -382,7 +382,7 @@
var/list/designs = list()
- var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
+ var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
for(var/datum/design/design in cached_designs)
diff --git a/code/modules/vehicles/mecha/mecha_ui.dm b/code/modules/vehicles/mecha/mecha_ui.dm
index ca7f03130e0..6f2aab1a657 100644
--- a/code/modules/vehicles/mecha/mecha_ui.dm
+++ b/code/modules/vehicles/mecha/mecha_ui.dm
@@ -29,7 +29,7 @@
/obj/vehicle/sealed/mecha/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/mecha_equipment),
+ get_asset_datum(/datum/asset/spritesheet_batched/mecha_equipment),
)
/obj/vehicle/sealed/mecha/ui_static_data(mob/user)
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 1e13e5f03c1..46d9d23a7f6 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -1207,7 +1207,7 @@ GLOBAL_LIST_EMPTY(vending_machines_to_restock)
/obj/machinery/vending/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/vending),
+ get_asset_datum(/datum/asset/spritesheet_batched/vending),
)
/obj/machinery/vending/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/modules/wiremod/core/component_printer.dm b/code/modules/wiremod/core/component_printer.dm
index 923b35644e4..ee96af3530e 100644
--- a/code/modules/wiremod/core/component_printer.dm
+++ b/code/modules/wiremod/core/component_printer.dm
@@ -88,8 +88,8 @@
/obj/machinery/component_printer/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
- get_asset_datum(/datum/asset/spritesheet/research_designs)
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/component_printer/RefreshParts()
@@ -178,7 +178,7 @@
var/list/designs = list()
- var/datum/asset/spritesheet/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet/research_designs)
+ var/datum/asset/spritesheet_batched/research_designs/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
var/size32x32 = "[spritesheet.name]32x32"
// for (var/datum/design/component/component_design_type as anything in subtypesof(/datum/design/component))
@@ -286,8 +286,8 @@
/obj/machinery/debug_component_printer/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
- get_asset_datum(/datum/asset/spritesheet/research_designs)
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/debug_component_printer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
@@ -351,8 +351,8 @@
/obj/machinery/module_duplicator/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/sheetmaterials),
- get_asset_datum(/datum/asset/spritesheet/research_designs)
+ get_asset_datum(/datum/asset/spritesheet_batched/sheetmaterials),
+ get_asset_datum(/datum/asset/spritesheet_batched/research_designs)
)
/obj/machinery/module_duplicator/RefreshParts()
diff --git a/config/config.txt b/config/config.txt
index 997a9487fe0..1872e73a68f 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -564,6 +564,14 @@ MOTD motd.txt
## but enabled on production (the default).
CACHE_ASSETS 0
+## Enables 'smart' asset caching, for assets that support it.
+## This is a type of asset cache that automatically invalidates itself based on inputs to the asset generation.
+## The cache is stored in `data/spritesheets/smart_cache/`.
+## It lowers the generation cost and is safe to enable on development and production.
+## This cache is only cleared by the game or manually, but it shouldn't affect the results.
+## This setting is independent of `CACHE_ASSETS`, they do not affect each other whatsoever.
+SMART_CACHE_ASSETS
+
## If this is uncommented, we will save all associated spritesheet PNGs and CSS files to a folder in the round-specific logs folder.
## Useful for developers to debug potential spritesheet issues to determine where the issue is cropping up (either in DM-side sprite generation or in the TGUI-side display of said spritesheet).
## Will only seek to waste disk space if ran on production.
diff --git a/icons/ui/condiments/bbqsauce.png b/icons/ui/condiments/bbqsauce.png
deleted file mode 100644
index c934c2d60ed..00000000000
Binary files a/icons/ui/condiments/bbqsauce.png and /dev/null differ
diff --git a/icons/ui/condiments/bottle.png b/icons/ui/condiments/bottle.png
deleted file mode 100644
index 1a73419e727..00000000000
Binary files a/icons/ui/condiments/bottle.png and /dev/null differ
diff --git a/icons/ui/condiments/cherryjelly.png b/icons/ui/condiments/cherryjelly.png
deleted file mode 100644
index 96ea15cb303..00000000000
Binary files a/icons/ui/condiments/cherryjelly.png and /dev/null differ
diff --git a/icons/ui/condiments/coldsauce.png b/icons/ui/condiments/coldsauce.png
deleted file mode 100644
index 1ba6af6847b..00000000000
Binary files a/icons/ui/condiments/coldsauce.png and /dev/null differ
diff --git a/icons/ui/condiments/condi_empty.png b/icons/ui/condiments/condi_empty.png
deleted file mode 100644
index cbdfceabfb3..00000000000
Binary files a/icons/ui/condiments/condi_empty.png and /dev/null differ
diff --git a/icons/ui/condiments/cookingoil.png b/icons/ui/condiments/cookingoil.png
deleted file mode 100644
index dc8a192e401..00000000000
Binary files a/icons/ui/condiments/cookingoil.png and /dev/null differ
diff --git a/icons/ui/condiments/enzyme.png b/icons/ui/condiments/enzyme.png
deleted file mode 100644
index a72a83a5c6c..00000000000
Binary files a/icons/ui/condiments/enzyme.png and /dev/null differ
diff --git a/icons/ui/condiments/flour.png b/icons/ui/condiments/flour.png
deleted file mode 100644
index bcbb22db3fc..00000000000
Binary files a/icons/ui/condiments/flour.png and /dev/null differ
diff --git a/icons/ui/condiments/honey.png b/icons/ui/condiments/honey.png
deleted file mode 100644
index 7cf510d6f5b..00000000000
Binary files a/icons/ui/condiments/honey.png and /dev/null differ
diff --git a/icons/ui/condiments/hotsauce.png b/icons/ui/condiments/hotsauce.png
deleted file mode 100644
index c243da054aa..00000000000
Binary files a/icons/ui/condiments/hotsauce.png and /dev/null differ
diff --git a/icons/ui/condiments/ketchup.png b/icons/ui/condiments/ketchup.png
deleted file mode 100644
index 4896289ec23..00000000000
Binary files a/icons/ui/condiments/ketchup.png and /dev/null differ
diff --git a/icons/ui/condiments/mayonnaise.png b/icons/ui/condiments/mayonnaise.png
deleted file mode 100644
index 0d6301b8935..00000000000
Binary files a/icons/ui/condiments/mayonnaise.png and /dev/null differ
diff --git a/icons/ui/condiments/milk.png b/icons/ui/condiments/milk.png
deleted file mode 100644
index e0c4cf03cc8..00000000000
Binary files a/icons/ui/condiments/milk.png and /dev/null differ
diff --git a/icons/ui/condiments/oliveoil.png b/icons/ui/condiments/oliveoil.png
deleted file mode 100644
index ee1bea5bf9b..00000000000
Binary files a/icons/ui/condiments/oliveoil.png and /dev/null differ
diff --git a/icons/ui/condiments/peanutbutter.png b/icons/ui/condiments/peanutbutter.png
deleted file mode 100644
index a9d048d5e9a..00000000000
Binary files a/icons/ui/condiments/peanutbutter.png and /dev/null differ
diff --git a/icons/ui/condiments/peppermillsmall.png b/icons/ui/condiments/peppermillsmall.png
deleted file mode 100644
index 8cd77e2ceeb..00000000000
Binary files a/icons/ui/condiments/peppermillsmall.png and /dev/null differ
diff --git a/icons/ui/condiments/rice.png b/icons/ui/condiments/rice.png
deleted file mode 100644
index b49fb842979..00000000000
Binary files a/icons/ui/condiments/rice.png and /dev/null differ
diff --git a/icons/ui/condiments/saltshakersmall.png b/icons/ui/condiments/saltshakersmall.png
deleted file mode 100644
index 70b17e2cb55..00000000000
Binary files a/icons/ui/condiments/saltshakersmall.png and /dev/null differ
diff --git a/icons/ui/condiments/soymilk.png b/icons/ui/condiments/soymilk.png
deleted file mode 100644
index 1117e532fce..00000000000
Binary files a/icons/ui/condiments/soymilk.png and /dev/null differ
diff --git a/icons/ui/condiments/soysauce.png b/icons/ui/condiments/soysauce.png
deleted file mode 100644
index aafb139f323..00000000000
Binary files a/icons/ui/condiments/soysauce.png and /dev/null differ
diff --git a/icons/ui/condiments/sugar.png b/icons/ui/condiments/sugar.png
deleted file mode 100644
index 3cb1264ed35..00000000000
Binary files a/icons/ui/condiments/sugar.png and /dev/null differ
diff --git a/icons/ui/pda/pda_atmos.png b/icons/ui/pda/pda_atmos.png
deleted file mode 100644
index 2edb09f4f3e..00000000000
Binary files a/icons/ui/pda/pda_atmos.png and /dev/null differ
diff --git a/icons/ui/pda/pda_back.png b/icons/ui/pda/pda_back.png
deleted file mode 100644
index f84178b9f59..00000000000
Binary files a/icons/ui/pda/pda_back.png and /dev/null differ
diff --git a/icons/ui/pda/pda_bell.png b/icons/ui/pda/pda_bell.png
deleted file mode 100644
index 5f6bf32369f..00000000000
Binary files a/icons/ui/pda/pda_bell.png and /dev/null differ
diff --git a/icons/ui/pda/pda_blank.png b/icons/ui/pda/pda_blank.png
deleted file mode 100644
index 14c58ad5d7a..00000000000
Binary files a/icons/ui/pda/pda_blank.png and /dev/null differ
diff --git a/icons/ui/pda/pda_boom.png b/icons/ui/pda/pda_boom.png
deleted file mode 100644
index eba7df46ba8..00000000000
Binary files a/icons/ui/pda/pda_boom.png and /dev/null differ
diff --git a/icons/ui/pda/pda_bucket.png b/icons/ui/pda/pda_bucket.png
deleted file mode 100644
index cccf676abca..00000000000
Binary files a/icons/ui/pda/pda_bucket.png and /dev/null differ
diff --git a/icons/ui/pda/pda_chatroom.png b/icons/ui/pda/pda_chatroom.png
deleted file mode 100644
index ed8eb8fd2bb..00000000000
Binary files a/icons/ui/pda/pda_chatroom.png and /dev/null differ
diff --git a/icons/ui/pda/pda_cleanbot.png b/icons/ui/pda/pda_cleanbot.png
deleted file mode 100644
index 65c37b418ae..00000000000
Binary files a/icons/ui/pda/pda_cleanbot.png and /dev/null differ
diff --git a/icons/ui/pda/pda_color.png b/icons/ui/pda/pda_color.png
deleted file mode 100644
index dfa5b3b03d2..00000000000
Binary files a/icons/ui/pda/pda_color.png and /dev/null differ
diff --git a/icons/ui/pda/pda_crate.png b/icons/ui/pda/pda_crate.png
deleted file mode 100644
index 98b5853c36f..00000000000
Binary files a/icons/ui/pda/pda_crate.png and /dev/null differ
diff --git a/icons/ui/pda/pda_cuffs.png b/icons/ui/pda/pda_cuffs.png
deleted file mode 100644
index fd830f48cfb..00000000000
Binary files a/icons/ui/pda/pda_cuffs.png and /dev/null differ
diff --git a/icons/ui/pda/pda_droneblacklist.png b/icons/ui/pda/pda_droneblacklist.png
deleted file mode 100644
index 98fd4a444ef..00000000000
Binary files a/icons/ui/pda/pda_droneblacklist.png and /dev/null differ
diff --git a/icons/ui/pda/pda_dronephone.png b/icons/ui/pda/pda_dronephone.png
deleted file mode 100644
index 78da8a89117..00000000000
Binary files a/icons/ui/pda/pda_dronephone.png and /dev/null differ
diff --git a/icons/ui/pda/pda_eject.png b/icons/ui/pda/pda_eject.png
deleted file mode 100644
index a9323f8f9f7..00000000000
Binary files a/icons/ui/pda/pda_eject.png and /dev/null differ
diff --git a/icons/ui/pda/pda_emoji.png b/icons/ui/pda/pda_emoji.png
deleted file mode 100644
index 9bc33cdee35..00000000000
Binary files a/icons/ui/pda/pda_emoji.png and /dev/null differ
diff --git a/icons/ui/pda/pda_exit.png b/icons/ui/pda/pda_exit.png
deleted file mode 100644
index 68ef0c9e06d..00000000000
Binary files a/icons/ui/pda/pda_exit.png and /dev/null differ
diff --git a/icons/ui/pda/pda_flashlight.png b/icons/ui/pda/pda_flashlight.png
deleted file mode 100644
index e0a6042c48b..00000000000
Binary files a/icons/ui/pda/pda_flashlight.png and /dev/null differ
diff --git a/icons/ui/pda/pda_floorbot.png b/icons/ui/pda/pda_floorbot.png
deleted file mode 100644
index 48cd618f008..00000000000
Binary files a/icons/ui/pda/pda_floorbot.png and /dev/null differ
diff --git a/icons/ui/pda/pda_font.png b/icons/ui/pda/pda_font.png
deleted file mode 100644
index 5561bd973ba..00000000000
Binary files a/icons/ui/pda/pda_font.png and /dev/null differ
diff --git a/icons/ui/pda/pda_honk.png b/icons/ui/pda/pda_honk.png
deleted file mode 100644
index 9e94b9d02bb..00000000000
Binary files a/icons/ui/pda/pda_honk.png and /dev/null differ
diff --git a/icons/ui/pda/pda_locked.PNG b/icons/ui/pda/pda_locked.PNG
deleted file mode 100644
index 571ba4317ae..00000000000
Binary files a/icons/ui/pda/pda_locked.PNG and /dev/null differ
diff --git a/icons/ui/pda/pda_mail.png b/icons/ui/pda/pda_mail.png
deleted file mode 100644
index 6904faec185..00000000000
Binary files a/icons/ui/pda/pda_mail.png and /dev/null differ
diff --git a/icons/ui/pda/pda_medbot.png b/icons/ui/pda/pda_medbot.png
deleted file mode 100644
index 4c7f178dd34..00000000000
Binary files a/icons/ui/pda/pda_medbot.png and /dev/null differ
diff --git a/icons/ui/pda/pda_medical.png b/icons/ui/pda/pda_medical.png
deleted file mode 100644
index 5a09e3e1369..00000000000
Binary files a/icons/ui/pda/pda_medical.png and /dev/null differ
diff --git a/icons/ui/pda/pda_menu.png b/icons/ui/pda/pda_menu.png
deleted file mode 100644
index 7eefb7d6ba9..00000000000
Binary files a/icons/ui/pda/pda_menu.png and /dev/null differ
diff --git a/icons/ui/pda/pda_mule.png b/icons/ui/pda/pda_mule.png
deleted file mode 100644
index 8218646c86d..00000000000
Binary files a/icons/ui/pda/pda_mule.png and /dev/null differ
diff --git a/icons/ui/pda/pda_notes.png b/icons/ui/pda/pda_notes.png
deleted file mode 100644
index 79ac48df10f..00000000000
Binary files a/icons/ui/pda/pda_notes.png and /dev/null differ
diff --git a/icons/ui/pda/pda_power.png b/icons/ui/pda/pda_power.png
deleted file mode 100644
index 1326a06259f..00000000000
Binary files a/icons/ui/pda/pda_power.png and /dev/null differ
diff --git a/icons/ui/pda/pda_rdoor.png b/icons/ui/pda/pda_rdoor.png
deleted file mode 100644
index ea0dbd96c47..00000000000
Binary files a/icons/ui/pda/pda_rdoor.png and /dev/null differ
diff --git a/icons/ui/pda/pda_reagent.png b/icons/ui/pda/pda_reagent.png
deleted file mode 100644
index 5baa932ff8d..00000000000
Binary files a/icons/ui/pda/pda_reagent.png and /dev/null differ
diff --git a/icons/ui/pda/pda_refresh.png b/icons/ui/pda/pda_refresh.png
deleted file mode 100644
index a2d04df60f7..00000000000
Binary files a/icons/ui/pda/pda_refresh.png and /dev/null differ
diff --git a/icons/ui/pda/pda_scanner.png b/icons/ui/pda/pda_scanner.png
deleted file mode 100644
index 9f1c71fc20b..00000000000
Binary files a/icons/ui/pda/pda_scanner.png and /dev/null differ
diff --git a/icons/ui/pda/pda_signaler.png b/icons/ui/pda/pda_signaler.png
deleted file mode 100644
index 6e32c12c0b6..00000000000
Binary files a/icons/ui/pda/pda_signaler.png and /dev/null differ
diff --git a/icons/ui/pda/pda_skills.png b/icons/ui/pda/pda_skills.png
deleted file mode 100644
index 25096c7a82c..00000000000
Binary files a/icons/ui/pda/pda_skills.png and /dev/null differ
diff --git a/icons/ui/pda/pda_status.png b/icons/ui/pda/pda_status.png
deleted file mode 100644
index 994dd76d528..00000000000
Binary files a/icons/ui/pda/pda_status.png and /dev/null differ
diff --git a/tgstation.dme b/tgstation.dme
index 74977b47be5..02a60665733 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -47,6 +47,7 @@
#include "code\__DEFINES\art.dm"
#include "code\__DEFINES\assemblies.dm"
#include "code\__DEFINES\assert.dm"
+#include "code\__DEFINES\assets.dm"
#include "code\__DEFINES\atom_hud.dm"
#include "code\__DEFINES\basic_mobs.dm"
#include "code\__DEFINES\basketball.dm"
@@ -3450,7 +3451,6 @@
#include "code\modules\asset_cache\assets\chemmaster.dm"
#include "code\modules\asset_cache\assets\circuits.dm"
#include "code\modules\asset_cache\assets\common.dm"
-#include "code\modules\asset_cache\assets\condiments.dm"
#include "code\modules\asset_cache\assets\contracts.dm"
#include "code\modules\asset_cache\assets\crafting.dm"
#include "code\modules\asset_cache\assets\emojipedia.dm"
@@ -3471,7 +3471,6 @@
#include "code\modules\asset_cache\assets\orbit.dm"
#include "code\modules\asset_cache\assets\paper.dm"
#include "code\modules\asset_cache\assets\particle_editor.dm"
-#include "code\modules\asset_cache\assets\pda.dm"
#include "code\modules\asset_cache\assets\permissions.dm"
#include "code\modules\asset_cache\assets\pipes.dm"
#include "code\modules\asset_cache\assets\plane_debug.dm"
@@ -3491,6 +3490,9 @@
#include "code\modules\asset_cache\assets\uplink.dm"
#include "code\modules\asset_cache\assets\vending.dm"
#include "code\modules\asset_cache\assets\vv.dm"
+#include "code\modules\asset_cache\spritesheet\batched\batched_spritesheet.dm"
+#include "code\modules\asset_cache\spritesheet\batched\universal_icon.dm"
+#include "code\modules\asset_cache\spritesheet\legacy\legacy_spritesheet.dm"
#include "code\modules\asset_cache\transports\asset_transport.dm"
#include "code\modules\asset_cache\transports\webroot_transport.dm"
#include "code\modules\atmospherics\environmental\LINDA_fire.dm"
diff --git a/tgui/packages/tgui/interfaces/Fabrication/Types.ts b/tgui/packages/tgui/interfaces/Fabrication/Types.ts
index fd652730196..fc032a7edae 100644
--- a/tgui/packages/tgui/interfaces/Fabrication/Types.ts
+++ b/tgui/packages/tgui/interfaces/Fabrication/Types.ts
@@ -64,7 +64,7 @@ export type Design = {
/**
* The icon used to represent this design, generated in
- * /datum/asset/spritesheet/research_designs. **The image within may not be
+ * /datum/asset/spritesheet_batched/research_designs. **The image within may not be
* 32x32.**
*/
icon: string;
diff --git a/tools/ci/run_server.sh b/tools/ci/run_server.sh
index e14d35cd9d4..178bd2c5b5d 100644
--- a/tools/ci/run_server.sh
+++ b/tools/ci/run_server.sh
@@ -6,8 +6,8 @@ MAP=$1
echo Testing $MAP
tools/deploy.sh ci_test
-mkdir ci_test/config
-mkdir ci_test/data
+mkdir -p ci_test/config
+mkdir -p ci_test/data
#test config
cp tools/ci/ci_config.txt ci_test/config/config.txt
diff --git a/tools/deploy.sh b/tools/deploy.sh
index b91eee1331e..8b2dba139f5 100755
--- a/tools/deploy.sh
+++ b/tools/deploy.sh
@@ -11,12 +11,8 @@ fi
mkdir -p \
$1/_maps \
- $1/icons/effects \
- $1/icons/mob/clothing \
- $1/icons/mob/inhands \
- $1/icons/mob/simple \
- $1/icons/obj \
- $1/icons/runtime \
+ $1/data/spritesheets \
+ $1/icons \
$1/sound/runtime \
$1/strings \
$1/tgui/public \
@@ -29,12 +25,7 @@ fi
cp tgstation.dmb tgstation.rsc $1/
cp -r _maps/* $1/_maps/
-cp -r icons/effects/* $1/icons/effects/
-cp -r icons/mob/clothing/* $1/icons/mob/clothing/
-cp -r icons/mob/inhands/* $1/icons/mob/inhands/
-cp -r icons/mob/simple/* $1/icons/mob/simple/
-cp -r icons/obj/* $1/icons/obj/
-cp -r icons/runtime/* $1/icons/runtime/
+cp -r icons/* $1/icons/
cp -r sound/runtime/* $1/sound/runtime/
cp -r strings/* $1/strings/
cp -r tgui/public/* $1/tgui/public/