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/