From cc335e7e9e250e32edadab3ab7d711eb9a8a0219 Mon Sep 17 00:00:00 2001 From: itsmeow Date: Mon, 3 Mar 2025 07:58:27 -0600 Subject: [PATCH] IconForge: rust-g Spritesheet Generation (#89478) ## About The Pull Request Replaces the asset subsystem's spritesheet generator with a rust-based implementation (https://github.com/tgstation/rust-g/pull/160). This is a rough port of https://github.com/BeeStation/BeeStation-Hornet/pull/10404, but it includes fixes for some cases I didn't catch that apply on TG. (FWIW we've been using this system on prod for over a year and encountered no major issues.) ### TG MAINTAINER NOTE ![image](https://github.com/user-attachments/assets/53bd2b44-9bb5-42d2-b33f-093651edebc0) ### Batched Spritesheets `/datum/asset/spritesheet_batched`: A version of the spritesheet system that collects a list of `/datum/universal_icon`s and sends them off to rustg asynchronously, and the generation also runs on another thread, so the game doesn't block during realize_spritesheet. The rust generation is about 10x faster when it comes to actual icon generation, but the biggest perk of the batched spritesheets is the caching system. This PR notably does not convert a few things to the new spritesheet generator. - Species and antagonist icons in the preferences view because they use getFlatIcon ~~which can't be converted to universal icons~~. - Yes, this is still a *massive* cost to init, unfortunately. On Bee, I actually enabled the 'legacy' cache on prod and development, which you can see in my PR. That's why I added the 'clear cache' verb and the `unregister()` procs, because it can force a regeneration at runtime. I decided not to port this, since I think it would be detrimental to the large amount of contributors here. - It is *technically* possible to port parts of this to the uni_icon system by making a uni_icon version of getFlatIcon. However, some overlays use runtime-generated icons which are ~~completely unparseable to IconForge, since they're stored in the RSC and don't exist as files anywhere~~. This is most noticeable with things like hair (which blend additively with the hair mask on the server, thus making them invisible to `get_flat_uni_icon`). It also doesn't help that species and antag icons will still need to generate a bunch of dummies and delete them to even verify cache validity. - It is actually possible to write the RSC icons to the filesystem (using fcopy) and reference them in IconForge. However, I'm going to wait on doing this until I port my GAGS implementation because it requires GAGS to exist on the filesystem as well. #### Caching IconForge generates a cache based on the set of icons used, all transform operations applied, and the source DMIs of each icon used within the spritesheet. It can compare the hashes and invalidate the cache automatically if any of these change. This means we can enable caching on development, and have absolutely no downsides, because if anything changes, the cache invalidates itself. The caching has a mean cost of ~5ms and saves a lot of time compared to generating the spritesheet, even with rust's faster generation. The main downside is that the cache still requires building the list of icons and their transforms, then json encoding it to send to rustg. Here's an abbreviated example of a cache JSON. All of these need to match for the cache to be valid. `input_hash` contains the transform definitions for all the sprites in the spritesheet, so if the input to iconforge changes, that hash catches it. The `sizes` and `sprites` are loaded into DM. ```json { "input_hash": "99f1bc67d590e000", "dmi_hashes": { "icons/ui/achievements/achievements.dmi": "771200c75da11c62" }, "sizes": [ "76x76" ], "sprites": { "achievement-rustascend": { "size_id": "76x76", "position": 1 } }, "rustg_version": "3.6.0", "dm_version": 1 } ``` ### Universal Icons Universal icons are just a collection of DMI, Icon State, and any icon transformation procs you apply (blends, crops, scales). They can be convered to DM icons via `to_icon()`. I've included an implementation of GAGS that produces universal icons, allowing GAGS items to be converted into them. IconForge can read universal icons and add them to spritesheets. It's basically just a wrapper that reimplements BYOND icon procs. ### Other Stuff Converts some uses of md5asfile within legacy spritesheets to use rustg_hash_file instead, improving the performance of their generation. Fixes lizard body markings not showing in previews, and re-adds eyes to the ethereal color preview. This is a side effect of IconForge having *much* better error handling than DM icon procs. Invalid stuff that gets passed around will error instead of silently doing nothing. Changes the CSS used in legacy spritesheet generation to split `background: url(...) no-repeat` into separate props. This is necessary for WebView2, as IE treats these properties differently - adding `background-color` to an icon object (as seen in the R&D console) won't work if you don't split these out. Deletes unused spritesheets and their associated icons (condiments spritesheet, old PDA spritesheet) ## Why It's Good For The Game If you press "Character Setup", the 10-13sec of lag is now approximately 0.5-2 seconds. Tracy profile showing the time spent on get_asset_datum. I pressed the preferences button during init on both branches. Do note that this was ran with a smart cache HIT, so no generation occurred. ![image](https://github.com/user-attachments/assets/3efa71ab-972b-4f5a-acab-0892496ef999) Much lower worst-case for /datum/asset/New (which includes `create_spritesheets()` and `register()`) ![image](https://github.com/user-attachments/assets/9ad8ceee-7bd6-4c48-b5f3-006520f527ef) Here's a look at the internal costs from rustg - as you can see `generate_spritesheet()` is very fast: ![image](https://github.com/user-attachments/assets/e6892c28-8c31-4af5-96d4-501e966d0ce9) ### Comparison for a single spritesheet - chat spritesheet: **Before** ![image](https://github.com/user-attachments/assets/cbd65787-42ba-4278-a45c-bd3d538da986) **After** ![image](https://github.com/user-attachments/assets/d750899a-bd07-4b57-80fb-420fcc0ae416) ## Changelog :cl: fix: Fixed lizard body markings and ethereal feature previews in the preference menu missing some overlays. refactor: Optimized spritesheet asset generation greatly using rustg IconForge, greatly reducing post-initialization lag as well as reducing init times and saving server computation. config: Added 'smart' asset caching, for batched rustg IconForge spritesheets. It is persistent and suitable for use on local, with automatic invalidation. add: Added admin verbs - Debug -> Clear Smart/Legacy Asset Cache for spritesheets. fix: Fixed R&D console icons breaking on WebView2/516 /:cl: --- code/__DEFINES/assets.dm | 16 + code/__HELPERS/_lists.dm | 15 + code/__HELPERS/files.dm | 1 + .../configuration/entries/general.dm | 3 + code/controllers/subsystem/asset_loading.dm | 6 + .../subsystem/processing/greyscale.dm | 10 + code/datums/achievements/_achievement_data.dm | 4 +- code/datums/browser.dm | 3 + code/datums/components/crafting/crafting.dm | 6 +- code/datums/greyscale/_greyscale_config.dm | 39 ++ code/datums/greyscale/layer.dm | 31 ++ code/game/machinery/autolathe.dm | 6 +- code/game/machinery/computer/arcade/orion.dm | 2 +- code/game/machinery/doors/airlock.dm | 8 + code/game/machinery/doors/door.dm | 2 + code/game/machinery/doors/firedoor.dm | 4 +- code/game/machinery/flatpacker.dm | 4 +- .../machinery/telecomms/computers/message.dm | 2 +- .../telecomms/computers/telemonitor.dm | 2 +- code/game/objects/items/airlock_painter.dm | 52 +-- code/game/objects/items/rcd/RCD.dm | 2 +- code/game/objects/items/rcd/RPD.dm | 2 +- code/game/objects/items/rcd/RPLD.dm | 2 +- code/game/objects/items/rcd/RTD.dm | 2 +- code/modules/admin/admin_verbs.dm | 27 ++ code/modules/admin/verbs/light_debug.dm | 4 +- code/modules/asset_cache/asset_list.dm | 406 ++--------------- .../asset_cache/assets/achievements.dm | 14 +- code/modules/asset_cache/assets/bibles.dm | 13 +- code/modules/asset_cache/assets/chat.dm | 13 +- code/modules/asset_cache/assets/chemmaster.dm | 6 +- code/modules/asset_cache/assets/condiments.dm | 24 - code/modules/asset_cache/assets/crafting.dm | 56 +-- code/modules/asset_cache/assets/emojipedia.dm | 6 +- code/modules/asset_cache/assets/fish.dm | 9 +- .../asset_cache/assets/light_templates.dm | 10 +- code/modules/asset_cache/assets/mafia.dm | 6 +- code/modules/asset_cache/assets/mecha.dm | 8 +- code/modules/asset_cache/assets/moods.dm | 41 +- code/modules/asset_cache/assets/pda.dm | 34 -- code/modules/asset_cache/assets/pipes.dm | 7 +- code/modules/asset_cache/assets/plumbing.dm | 6 +- code/modules/asset_cache/assets/rcd.dm | 25 +- .../asset_cache/assets/research_designs.dm | 28 +- code/modules/asset_cache/assets/rtd.dm | 10 +- code/modules/asset_cache/assets/seeds.dm | 9 +- .../asset_cache/assets/sheetmaterials.dm | 6 +- code/modules/asset_cache/assets/supplypods.dm | 18 +- code/modules/asset_cache/assets/tcomms.dm | 6 +- code/modules/asset_cache/assets/vending.dm | 33 +- .../batched/batched_spritesheet.dm | 365 +++++++++++++++ .../spritesheet/batched/universal_icon.dm | 374 ++++++++++++++++ .../spritesheet/legacy/legacy_spritesheet.dm | 418 ++++++++++++++++++ .../asset_cache/transports/asset_transport.dm | 8 +- .../transports/webroot_transport.dm | 8 +- code/modules/cargo/centcom_podlauncher.dm | 2 +- code/modules/client/preferences.dm | 2 +- code/modules/client/preferences/README.md | 6 +- .../modules/client/preferences/_preference.dm | 2 +- .../client/preferences/ai_core_display.dm | 4 +- .../client/preferences/ai_emote_display.dm | 4 +- .../client/preferences/ai_hologram_display.dm | 4 +- code/modules/client/preferences/assets.dm | 25 +- code/modules/client/preferences/clothing.dm | 56 +-- code/modules/client/preferences/ghost.dm | 2 +- code/modules/client/preferences/glasses.dm | 4 +- .../client/preferences/middleware/species.dm | 2 +- .../preferences/species_features/basic.dm | 22 +- .../preferences/species_features/ethereal.dm | 25 +- .../preferences/species_features/lizard.dm | 48 +- .../preferences/species_features/moth.dm | 50 +-- .../preferences/species_features/pod.dm | 16 +- .../preferences/species_features/vampire.dm | 4 +- code/modules/client/preferences/ui_style.dm | 8 +- code/modules/client/verbs/ooc.dm | 2 +- code/modules/emoji/emoji_parse.dm | 2 +- code/modules/fishing/fish_catalog.dm | 2 +- code/modules/hydroponics/biogenerator.dm | 2 +- code/modules/hydroponics/seed_extractor.dm | 2 +- code/modules/language/_language.dm | 2 +- code/modules/library/lib_machines.dm | 2 +- code/modules/mafia/controller_ui.dm | 2 +- code/modules/mining/machine_silo.dm | 2 +- .../file_system/programs/emojipedia.dm | 2 +- .../programs/messenger/messenger_program.dm | 2 +- .../file_system/programs/techweb.dm | 4 +- code/modules/paperwork/stamps.dm | 2 +- code/modules/plumbing/plumbers/pill_press.dm | 4 +- code/modules/research/designs.dm | 2 +- .../modules/research/machinery/_production.dm | 6 +- code/modules/research/rdconsole.dm | 4 +- code/modules/tgui/tgui_window.dm | 3 + code/modules/tgui_panel/tgui_panel.dm | 2 +- code/modules/transport/tram/tram_doors.dm | 1 + code/modules/unit_tests/_unit_tests.dm | 1 + code/modules/unit_tests/asset_smart_cache.dm | 65 +++ code/modules/unit_tests/preferences.dm | 2 +- code/modules/unit_tests/spritesheets.dm | 13 + .../modules/vehicles/mecha/mech_fabricator.dm | 6 +- code/modules/vehicles/mecha/mecha_ui.dm | 2 +- code/modules/vending/_vending.dm | 2 +- .../modules/wiremod/core/component_printer.dm | 14 +- config/config.txt | 8 + icons/ui/condiments/bbqsauce.png | Bin 429 -> 0 bytes icons/ui/condiments/bottle.png | Bin 387 -> 0 bytes icons/ui/condiments/cherryjelly.png | Bin 498 -> 0 bytes icons/ui/condiments/coldsauce.png | Bin 497 -> 0 bytes icons/ui/condiments/condi_empty.png | Bin 163 -> 0 bytes icons/ui/condiments/cookingoil.png | Bin 473 -> 0 bytes icons/ui/condiments/enzyme.png | Bin 458 -> 0 bytes icons/ui/condiments/flour.png | Bin 459 -> 0 bytes icons/ui/condiments/honey.png | Bin 461 -> 0 bytes icons/ui/condiments/hotsauce.png | Bin 376 -> 0 bytes icons/ui/condiments/ketchup.png | Bin 350 -> 0 bytes icons/ui/condiments/mayonnaise.png | Bin 346 -> 0 bytes icons/ui/condiments/milk.png | Bin 323 -> 0 bytes icons/ui/condiments/oliveoil.png | Bin 440 -> 0 bytes icons/ui/condiments/peanutbutter.png | Bin 415 -> 0 bytes icons/ui/condiments/peppermillsmall.png | Bin 331 -> 0 bytes icons/ui/condiments/rice.png | Bin 498 -> 0 bytes icons/ui/condiments/saltshakersmall.png | Bin 288 -> 0 bytes icons/ui/condiments/soymilk.png | Bin 334 -> 0 bytes icons/ui/condiments/soysauce.png | Bin 342 -> 0 bytes icons/ui/condiments/sugar.png | Bin 565 -> 0 bytes icons/ui/pda/pda_atmos.png | Bin 128 -> 0 bytes icons/ui/pda/pda_back.png | Bin 129 -> 0 bytes icons/ui/pda/pda_bell.png | Bin 147 -> 0 bytes icons/ui/pda/pda_blank.png | Bin 106 -> 0 bytes icons/ui/pda/pda_boom.png | Bin 185 -> 0 bytes icons/ui/pda/pda_bucket.png | Bin 127 -> 0 bytes icons/ui/pda/pda_chatroom.png | Bin 134 -> 0 bytes icons/ui/pda/pda_cleanbot.png | Bin 136 -> 0 bytes icons/ui/pda/pda_color.png | Bin 152 -> 0 bytes icons/ui/pda/pda_crate.png | Bin 137 -> 0 bytes icons/ui/pda/pda_cuffs.png | Bin 129 -> 0 bytes icons/ui/pda/pda_droneblacklist.png | Bin 153 -> 0 bytes icons/ui/pda/pda_dronephone.png | Bin 142 -> 0 bytes icons/ui/pda/pda_eject.png | Bin 133 -> 0 bytes icons/ui/pda/pda_emoji.png | Bin 135 -> 0 bytes icons/ui/pda/pda_exit.png | Bin 135 -> 0 bytes icons/ui/pda/pda_flashlight.png | Bin 124 -> 0 bytes icons/ui/pda/pda_floorbot.png | Bin 132 -> 0 bytes icons/ui/pda/pda_font.png | Bin 154 -> 0 bytes icons/ui/pda/pda_honk.png | Bin 125 -> 0 bytes icons/ui/pda/pda_locked.PNG | Bin 131 -> 0 bytes icons/ui/pda/pda_mail.png | Bin 151 -> 0 bytes icons/ui/pda/pda_medbot.png | Bin 140 -> 0 bytes icons/ui/pda/pda_medical.png | Bin 116 -> 0 bytes icons/ui/pda/pda_menu.png | Bin 133 -> 0 bytes icons/ui/pda/pda_mule.png | Bin 135 -> 0 bytes icons/ui/pda/pda_notes.png | Bin 126 -> 0 bytes icons/ui/pda/pda_power.png | Bin 124 -> 0 bytes icons/ui/pda/pda_rdoor.png | Bin 123 -> 0 bytes icons/ui/pda/pda_reagent.png | Bin 125 -> 0 bytes icons/ui/pda/pda_refresh.png | Bin 133 -> 0 bytes icons/ui/pda/pda_scanner.png | Bin 158 -> 0 bytes icons/ui/pda/pda_signaler.png | Bin 138 -> 0 bytes icons/ui/pda/pda_skills.png | Bin 127 -> 0 bytes icons/ui/pda/pda_status.png | Bin 133 -> 0 bytes tgstation.dme | 6 +- .../tgui/interfaces/Fabrication/Types.ts | 2 +- tools/ci/run_server.sh | 4 +- tools/deploy.sh | 15 +- 163 files changed, 1828 insertions(+), 870 deletions(-) create mode 100644 code/__DEFINES/assets.dm delete mode 100644 code/modules/asset_cache/assets/condiments.dm delete mode 100644 code/modules/asset_cache/assets/pda.dm create mode 100644 code/modules/asset_cache/spritesheet/batched/batched_spritesheet.dm create mode 100644 code/modules/asset_cache/spritesheet/batched/universal_icon.dm create mode 100644 code/modules/asset_cache/spritesheet/legacy/legacy_spritesheet.dm create mode 100644 code/modules/unit_tests/asset_smart_cache.dm delete mode 100644 icons/ui/condiments/bbqsauce.png delete mode 100644 icons/ui/condiments/bottle.png delete mode 100644 icons/ui/condiments/cherryjelly.png delete mode 100644 icons/ui/condiments/coldsauce.png delete mode 100644 icons/ui/condiments/condi_empty.png delete mode 100644 icons/ui/condiments/cookingoil.png delete mode 100644 icons/ui/condiments/enzyme.png delete mode 100644 icons/ui/condiments/flour.png delete mode 100644 icons/ui/condiments/honey.png delete mode 100644 icons/ui/condiments/hotsauce.png delete mode 100644 icons/ui/condiments/ketchup.png delete mode 100644 icons/ui/condiments/mayonnaise.png delete mode 100644 icons/ui/condiments/milk.png delete mode 100644 icons/ui/condiments/oliveoil.png delete mode 100644 icons/ui/condiments/peanutbutter.png delete mode 100644 icons/ui/condiments/peppermillsmall.png delete mode 100644 icons/ui/condiments/rice.png delete mode 100644 icons/ui/condiments/saltshakersmall.png delete mode 100644 icons/ui/condiments/soymilk.png delete mode 100644 icons/ui/condiments/soysauce.png delete mode 100644 icons/ui/condiments/sugar.png delete mode 100644 icons/ui/pda/pda_atmos.png delete mode 100644 icons/ui/pda/pda_back.png delete mode 100644 icons/ui/pda/pda_bell.png delete mode 100644 icons/ui/pda/pda_blank.png delete mode 100644 icons/ui/pda/pda_boom.png delete mode 100644 icons/ui/pda/pda_bucket.png delete mode 100644 icons/ui/pda/pda_chatroom.png delete mode 100644 icons/ui/pda/pda_cleanbot.png delete mode 100644 icons/ui/pda/pda_color.png delete mode 100644 icons/ui/pda/pda_crate.png delete mode 100644 icons/ui/pda/pda_cuffs.png delete mode 100644 icons/ui/pda/pda_droneblacklist.png delete mode 100644 icons/ui/pda/pda_dronephone.png delete mode 100644 icons/ui/pda/pda_eject.png delete mode 100644 icons/ui/pda/pda_emoji.png delete mode 100644 icons/ui/pda/pda_exit.png delete mode 100644 icons/ui/pda/pda_flashlight.png delete mode 100644 icons/ui/pda/pda_floorbot.png delete mode 100644 icons/ui/pda/pda_font.png delete mode 100644 icons/ui/pda/pda_honk.png delete mode 100644 icons/ui/pda/pda_locked.PNG delete mode 100644 icons/ui/pda/pda_mail.png delete mode 100644 icons/ui/pda/pda_medbot.png delete mode 100644 icons/ui/pda/pda_medical.png delete mode 100644 icons/ui/pda/pda_menu.png delete mode 100644 icons/ui/pda/pda_mule.png delete mode 100644 icons/ui/pda/pda_notes.png delete mode 100644 icons/ui/pda/pda_power.png delete mode 100644 icons/ui/pda/pda_rdoor.png delete mode 100644 icons/ui/pda/pda_reagent.png delete mode 100644 icons/ui/pda/pda_refresh.png delete mode 100644 icons/ui/pda/pda_scanner.png delete mode 100644 icons/ui/pda/pda_signaler.png delete mode 100644 icons/ui/pda/pda_skills.png delete mode 100644 icons/ui/pda/pda_status.png 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 c934c2d60ed051acf48d1a31871c3935a2bbdb92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^T<>7Fi*ArY;~ z2@&z$uWtKSpYrdWwD?J*?$(&ozE5vnbou&!@?(xkrU~0+nEdSjcoumW zOnk+V#k@&I;naDrDH4VaYfK8Bzbt1H&GZx5n7n=8rSptDK(OBaU)ql!Px+aF;PC48 z|4*2<)v!ybhc5aMzQue}Dqm#g0nPQ(9D}2qAJ$chH80!72ExuRskVP*7?=+>?%}yz zGe^U)$%K7J){Fn)!f9yxQp=hH3>WFzOsS* z19IscUvqW?AYf@_1yK`nEt#2Fbr)^bzP|X$_Xi&wHl39MhHDBlLq%QbtlS@ZbAjQ{ N;OXk;vd$@?2>`Exu~Gm4 diff --git a/icons/ui/condiments/bottle.png b/icons/ui/condiments/bottle.png deleted file mode 100644 index 1a73419e727b59aff5fbd3ca8d9ca9fa80629ff9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 387 zcmV-}0et?6P)Px$JxN4CR9J=WmN8DlFcgOWBBnBB%N0CFS+J69xj+VPz-YQwnGtJX&w~E~pdcGDoF^*(YMx#zajnAY0Zpkzwb6 zM21LY=(=Xio|6f1_H)VBcY+g9DM1!NI)VPCMHF<+i5RozF<3U_Z1fZ4B%GSF3t$zZ zMz^D5JxBbFfJV2wD)`Gdm6NYvt9P~kSOU85PKEp$?kk?@KSlsBvxoqY%Q5RGB2LRe hvEjS}9*^f=@CCNHZ?S-u_vrut002ovPDHLkV1oT9r@R0F diff --git a/icons/ui/condiments/cherryjelly.png b/icons/ui/condiments/cherryjelly.png deleted file mode 100644 index 96ea15cb3030db18c7edfe84796af69aba5da85e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 498 zcmVPx$tVu*cR9J=Wma$6$Q5?s=N!L(tI%Icr2oC9DlVCU~9JthE(9|gR4`^V^wS|ik z`431CK?DW5wFnL_Du^I1wg`5jw4{)O4%bk3b?V@IaxL9wx_jLFeLwejzjp^%Sp0X^ zXeWx%Q}y?Ksc=tKYn?XyBytD<5I9~NR%s-NCoBqA27Lg)Q}LO2C2XVu8nfZF5DQeB z4T$03T)4pTs5Tqs3S8_RsQ_^Il!wRbh8PY);CKiekIHwr_RO*nZ^A2w2M-J0w9;(g z%;qL7N7n_3(FE5s5~0>R1eSp^nwlq|3@14D*|EzqNW6~G8~%g^e0C_KDXQHM^8^4O z-x@+-8AN?{^w_CkTp3PC%aIHCQ*n5_ZWN@XVY&UOIJiL{^~+mx1vEmr3|`tD+Uctc z06?yEu3t-HnyLo%Nm!PWdZ;9(4Kk(+l%*sBu1(bCEOs8Zb%{R{2<0-Ub;ny$3lM!eW6p>w*f$`s%v=}L<*a@V+`JW+u1ows5OYRU061`M o8O{Nka4LaW5e?E{3kwU4PrmhPx$t4TybR9J=Wmd`6hQ545NnEWVYXPn=DWN1b-w5GPUoI`A*-w`$QuYM zZy>0wc-X8YZ*OWgmTD)mmLu`Wl>z`%l$Kiww+?W9cgtSk&v_;6PIj?vtDwl?0N`?S3+?3u zJ$*B6#!oZ{(i`)1e)NFR2inVt#7YL>o7g8h(+Yrb165Xexk~Mr_7XSY;XxGN#6FSm z3LU-cQX3h-T>m3u;bP|cADLY&WPB*|y8uRVfbpRWvx|kl3t&_Luv`Jta{5EcRqavw z{ev1_P%3_OrUph*%2n-Al1V5!1@^vCT0A}gq91#Dext++(L2uR4=Gnq_m?@4P9)LK nmreVL0O0$@BT1fBR{uip@a~BQ)0N2{00000NkvXXu0mjfDq`Jk diff --git a/icons/ui/condiments/condi_empty.png b/icons/ui/condiments/condi_empty.png deleted file mode 100644 index cbdfceabfb39e71531bcdadc48efee797fcd44c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{S0Mce3{IRl0i^%`|G$Qx zPx$lSxEDR9J=WmoZMmFcgOWBGxDaQWK*Lv8r}&&C~^z3$QWv5Z&4Xkd1?|^#ULc z;IdOy=}=)>i6P1X%#bEABvi4RvXCcvu%GS!%X=@e12p#Et!1)kVnO~Kdx*{UszvoX{eK zI=?o6)h>eT1B51_R>f5a0O*gvSG%ZQ03gZlJ_4-FFG4Uv!C#-t{l8hIPjMR3eFV4& z!9@6d4xu?*r)`68w)FudDai9mD7YGW6{bG|59U$jC^RW(QlNDqshWb?0r+Oy3g(cm zZ-daoDLBbtlp6CK*=gGdW<`MMJ=kh@!sA%AyFssT`FLEs{mp+tV|CjXWS-N?n*V>5 P00000NkvXXu0mjf=|<0p diff --git a/icons/ui/condiments/enzyme.png b/icons/ui/condiments/enzyme.png deleted file mode 100644 index a72a83a5c6cc73755ec5b35ff07bc5d1ffbde29a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 458 zcmV;*0X6=KP)Px$gh@m}R9J=Wls`*CQ5eR5W>@G!h9!i9&`^WY;2=mKEz!^iuptN%`U0&DB7T8B zLJiT-SBRDngfLn}g@*oIN+kaeB1+r_Q&1ba?+Gcm58UCrhx0r?-uIpZwrnXV7K)g$ zP{dU6m{AG$%yn5?Hc^2P0|0p48kJU0#e!Ms!A3{pq1*#pG&-6XK-_`Xy&{c{Mx&!q z-?^PW0DyVE%FFS)iiH}$Z|Hvl1XEt~V`XW$ZhPE>k6fawXc=HUTopn*0)KD=BcG8T zR|AM{B_?X$b>$}#Jw)m26vh1_hrSD*OIH|Vn7@CV4nW|{O)d%BcX5n-2Io^1=hvQg zssmk1exebqCnw+kez9whAAZnpe=lwwO4NZ|QiEh^q$UX$0{}EHH;2~{K(EVuERP_c zs5l0pPx$g-Jv~R9J=WmcL8GKorNnidkI>Qb;u>-D>faBb~r zdG`RD-DUy&N2D#_74G(ut^)?x({k>uAOWkV0fB?LLLs=^1zr zzk~^NBf`LxaD5YDJd&UrAqJ*Ij^_fLm5))WmADB47D6{7ygnx|0fR7x3_$5%>?hqP zFu-^u0a%gah1ca>>ogW-`9lx(2l>aOP@zJ5(>I~_u=xL*_Fe!0002ovPDHLkV1kXO B!@2+f diff --git a/icons/ui/condiments/honey.png b/icons/ui/condiments/honey.png deleted file mode 100644 index 7cf510d6f5b08eec9e26aebef6e4b9a7a79be8c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 461 zcmV;;0W$uHP)Px$heb_lR1hl zaC_LAXVSaxlCq#JKyW4xcLAV*y%vC$YQNZMe2i!!$alc9for>wY8(1gZ$t!VvVX~> z;7q=C;wSl?>NC?NtR98${R4HL1(JaN%UDTsy%vv$erTtneOUx&Qmg|@E8*O-)!+ay z8US7!pj<+&g@Q9#uZ4jgZekHhMgyQ&4%bvp^+jk`!O)NY)Uv;3U^GzI=#uFO(gZ{z z{^STE)|MA#WyerNB!gF*V|1RLNj|l!KyW5+n+8@l)pSR}i)Fw&GeGSHq%xd5-{d@N z7bLafD2Uev`pvlJ3kybSW&ApVf8TDW*>ZAna;EVP#4)G4_p%9f00000NkvXXu0mjf DUI@Px$GD$>1R9J=Wl(A03Fc5}65E4^IB!rYSEU;6Fp$w2GfH#1r$Xc;<=o_%X!p_*K z>Hq^0JU}`muEm7ZrQO322n_@z-_e#K{}kWZ_kXc{hFZ0hYIXf=rRG;JXPE|KT?^44 zj#TT#0IjZ{9iQv~&s2RVel3*+Q7{xEQ_C2qrULeYX6sk`9xdT(_A2c6vfup@^M18kj_ zO{EZGfY+P2G=98{f0(%-0D$LrQ>nQKAeyo^-)?so@wdE7J*Ua@s(Nx=&1=>A7kUF2 W9aq*Z-(3m-0000Px$7)eAyR9J=WmOXC5KoEsLqqJ_)L63xi3L%9v5C!og!5t{wa0~9?!tzBB6~sss zbT$n%9AGJ;1t&p~W)}*SedVq^@7t&G?1Gb%h2khtwl!XC%!lJXF;|GuWZ+5bluQ8t z()H$IJemwVlSHfl?ja+9pC`5H1u;MzB`RptzO4m~8gY~;lO7O%@VQDar%l%ziZJzN z@c1SynhwW(;41;$kWz_$!_Y@*2yIw24t>=8BuiD4K)c#vH=Ewv+s&r5t1U@6K-dGJ zkLq>aS!|cT_A0Ms7TYD#1jLgtJLG)4bpgnad$L0ga1-hw0NPl?WXIgdSnD|_p#yLL w4&eU+%ujfKja=zH00s{dxL(V)=1xukXZXZp#!nQ7p#T5?07*qoM6N<$f=5u0#sB~S diff --git a/icons/ui/condiments/mayonnaise.png b/icons/ui/condiments/mayonnaise.png deleted file mode 100644 index 0d6301b8935a100c12f5dea39d73d1cd51c13b88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 346 zcmV-g0j2(lP)Px$6iGxuR9J=Wma$I5Fc5~nBBo5Rg^^APWntwFkTMi1!3&^143EIj-Gk+YfrT|I z9onW7VL;HWeS>2_i9$h@&ZQt!|B_|NXW#$%hO;3kXboB8 zx&lqpi_0$XQGsGS)uz$|+}-x1f><8SxJJ90&HD#!AP15!u{g>hYHR&<0Q0vePR_1* z9u9~jn3s7y@5P6dj}My#09edQPO}>TUSIB6%t`<+b1#6(xPqwweDyy&VE_mK0bqLp svc7GtYPnh8ZvI08-#@!P=0QOK@3C2W?OPx#07*qoM6N<$g0-KPKmY&$ diff --git a/icons/ui/condiments/milk.png b/icons/ui/condiments/milk.png deleted file mode 100644 index e0c4cf03cc8d0c2b9a08213953c1e07255f688e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)Px#{YgYYR9J;$U>F6XU=$26U?fHH+Li176QeFTJdkK}Sx9jJ0|NttrnUxN`RiA2 zkYpYcsSdyfJiBi*cy`|=OD{GT~DEQR2f`QM#7{b1A zmO<0YfFuV{90f<0MlgK3aF*fIg|iGtmqw7RpDKy)-Gv4?o76-|avC6H!GPofN&s07 zK+kgIWAK|0RT3n VO*?708;k$|002ovPDHLkV1l$}fC~Ts diff --git a/icons/ui/condiments/oliveoil.png b/icons/ui/condiments/oliveoil.png deleted file mode 100644 index ee1bea5bf9bf083ba4e6164bde7ffe9bd9899b68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440 zcmV;p0Z0CcP)Px$a!Eu%R9J=WmAg&?Q4oed&t`1PVx`pif zkdA09#bYTOsIA;kC+u{bnR? z#h-(Bx)rI7yadNb2Lto)q9P4%OE@zqTVv3HgbSOGDad9>yP*M90u6jkfr>q i#Lw}lb;)z=%GE1&foft=iRbVD0000Px$SxH1eR9J=Wl|4wqKp2J}MR0N{l(wUTbm<~49R({L1XqVH4uXTY#3hTnQx~U# zqMN(YE~PjL9r^?9m?4>(MoVaLHx61c5=*|r=wROExV!J(_u(#=gG7lE{jnBQ7HKQ^ zVdK?jx!;mX_-=70e6n5lr4XXxx#91+ZP!IR5xFo*xC+E#RDif70?4JZKT@L%V7ueF z0l8F`uF)3(h;KpH=(Dl59y({cW2s&A9a!DV&@p-e5ZfIGT7Z#j!)6Qs0A;Q47VwQa zMvvvvi^NiX1czr^)bs|x2h6O^SJGIH6EDCF0>F9ibt2q0HW9M{2=4+m$rfM zZ@_XK?gs;^cQL$%VGv($!Bw*<3ReP{L4=JTUpPvBi4y&9y#l7ThF&Lbqi+BJ002ov JPDHLkV1gK>v`GK} diff --git a/icons/ui/condiments/peppermillsmall.png b/icons/ui/condiments/peppermillsmall.png deleted file mode 100644 index 8cd77e2ceeb8da49ee464a1e01bc0ecce8655e93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 331 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^TezcLC6N(Vo@xaQgB|6WDHZja*m=LQaCQXfjo(;oc!I_u!|yE7dp_0Rmr zXHoozp}BJ@^Wm9~*(I*!-S^tYe4>kqjg75MIDH+^if>&HPi_w9xwPn0igm*qfe90Z zk}E#F5pC|7B60Zgb^e#{8D>0X7Gv^{w_oDLF(FpmrHyr=$llZ8Yn5-W2{hnxJE$RX blYt>?Z^-fRua~uep~K+m>gTe~DWM4f^#_Ci diff --git a/icons/ui/condiments/rice.png b/icons/ui/condiments/rice.png deleted file mode 100644 index b49fb8429793f851771762484f5b49d16084f810..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 498 zcmVPx$tVu*cR9J=WmOW3xP!xt=zv5z4902(Ui|JsRU~?6x#t=8tXyRgYVKDv%1_xze z9iT)M+RH~|>9d?BJ?A~|J@+InU}Ix5 zWoBp-LvcxeE5E|UxM3Bg%Mi8h98FRnlGe{Jczms{;9zgVGCsF{PUMDe z5AduL06`^bo@Z)6$;bdmM{GE9rADWI3Egh6cSNc>ZZ(_a%xOQG%RT zLAHaaRW&NziA=y~Zk$}stMK_flQqBzq}x=<$UPI;^e*Bt`vjlg(>3w0699ms810%c zA`R3#FsLyAWJ@jVvr8z7Q4~eP5C9yFXJC1Yz{R*NMLwB3WK(BYcP#v=Z>r|CmY+*U o@JhFIFo32Sqy90mv9XyN-)(`OLn2y} z6C_v{Cy4YkF|zeM$@=yPn_c@yCr*+hdbj`1uCRCKXR&n+zU})aCjPx$2uVaiR9J;$U>F6XU=$26U?fHH+Li176QeFTJdkK}Sx9jJ0|NttrnUxN`RiA2 zkYpYcsSdyfVmG-n#BOpYOD{=3#;fLig-z(8VcD3UJp zA6Dw&0uLX4VR$q19nt1er81%fkQ4<4;-w5hegSyZtXs8#p@yN7HV%0D<}t(MX_HCO gG73h)C>Zvg!d;kCd07*qoM6N<$f}n+k=l}o! diff --git a/icons/ui/condiments/soysauce.png b/icons/ui/condiments/soysauce.png deleted file mode 100644 index aafb139f3231d9aa82960d9ee757d17116b8e8c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 342 zcmV-c0jd6pP)Px$5J^NqR9J=Wm9c69K@>&L1}kAPC}CL%f`zppDe?oU0%=pH$p_e`w)F#S{R4w# zdJ7R!TVz>+%Mc6+3)@aAWut|9S0aHq)y#wU=JMv<8E`lpC&p6~yFNC*@26TVDXHsY zuUeg7vFUs!i8xaP++E-972AiBk~Er(w+BBz{F-1z~HUt>-J}7#NvVi7&04AXs1iTqT{H$5=VB4%_U=!7f2lwxlZ!ad> oimUH{{1Px$?@2^KR9J;$U>F6XU=$26U?f)I+O=!{-@0^}K}}DWfe;-W989#i%)}^auW$J8 z8yLvI&CkzZU|>Ly7UAJ-*n99mBQa(Zbm`i)YyVqWSuxzceVgI+>(>Mg6crU^P*qiB zICkt9Q6Yg>L3@3}e`{waqAbSdkfVF|F;q4-;I)GVpQN?36N9R%D#?~JFfbtWH8$W? zht~l|_wGYAj0AA|_HDR+vK_#{z(7{;lcJBH11NC>)f}*O)oKO?1_lQ2uuwP~#;1z| zkS#*DlzIMk6#m~tPm&zKM2w=Xt5(A?y5**ZW(<<7JPcmB=?qK6LKyC6v{K&z3=G(P zzC2(Gx+>j&7j5llp&8u2ap{C3=9kmM{-mdULQHnpyTGmAfuzj@Z`!hhQ#L| zh&7Udfq_5>uuj{=V38rkaAns%1|2si6uLKJDns##Yz8}}3&ZX<&tKR$apC@niA~B{U8yr3oVs8?eWBxog^pq?*g6`z7z?EM U4vNe_x*BAVr>mdKI;Vst0Bz7EbN~PV diff --git a/icons/ui/pda/pda_back.png b/icons/ui/pda/pda_back.png deleted file mode 100644 index f84178b9f598507bb7880b33b47f1ce02d991d11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE-Ov7sn8e>&ZX<&tKRm*fL8(@|J|;FA2$K|5I=9J2|j*+-959w1QEKjiEk; V|MAPceZe4;JYD@<);T3K0RTAcBgOy# diff --git a/icons/ui/pda/pda_bell.png b/icons/ui/pda/pda_bell.png deleted file mode 100644 index 5f6bf32369fbbf2b83f2ecf839bd4111394a666a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|u0Xn>0Z1_DnEG}BDW;Mj zzhDNFaCOUGAkW;>#W95AdU6CS3lA?3j}H&853fU$Go#-M22NgHg^LG{9Jz3W!9^hH m>MFGbe0pjU2N@I(xG^$p4iMDdQMxn>WU!~JpUXO@geCwO@*)HP diff --git a/icons/ui/pda/pda_blank.png b/icons/ui/pda/pda_blank.png deleted file mode 100644 index 14c58ad5d7ab9a13843fb6601d27d11342b78e42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9>wyCYfK2V{3zq>Y#*!ev zU z{DK)cL?YeI)V55UHVG)?>FMGa!g1ZU_ayHD2M*@v&$$-ux$MH_;rj1lT|~n529rZF zM>v9o#AL6f$9(ver`^5ULEzO|4Q(Bhk__OpqbJ*SN{@0QA+B0xhK NJYD@<);T3K0RYo3IeY*B diff --git a/icons/ui/pda/pda_bucket.png b/icons/ui/pda/pda_bucket.png deleted file mode 100644 index cccf676abcac10e723df25c154dfc4715053f6a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE;yC7sn8e>&ZX<&tKR$ap7M@=XQm|EPevB{!d=`Us;B6sRG9eo)r=d$Bado TqAkB@g3R%B^>bP0l+XkK`ePz5 diff --git a/icons/ui/pda/pda_chatroom.png b/icons/ui/pda/pda_chatroom.png deleted file mode 100644 index ed8eb8fd2bbde4568019f966500eecbb2a5e407c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE+zf7sn8e>&ZX<&tKRm*z)bF(VPFLFVsv}xKXghibYbb%*f{xi;@EK3D#AN aT5Jr9P72)au)4h$WSpm~pUXO@geCw<0Vdf1 diff --git a/icons/ui/pda/pda_cleanbot.png b/icons/ui/pda/pda_cleanbot.png deleted file mode 100644 index 65c37b418aea091f8c052acfee2a2daa6f46033b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 136 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE)u)7sn8e>&ZX<&tKR$G4Y#wo9Y}M{Rqjh4Mt!7r)K;=c_HuQg*eV6llm5g c1x5>!7!Dm5uG*_qy#-{Tr>mdKI;Vst0KxVtod5s; diff --git a/icons/ui/pda/pda_color.png b/icons/ui/pda/pda_color.png deleted file mode 100644 index dfa5b3b03d248d8057c7d0597974350afe7eef72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|u0Xn>0Z1_DnEG}BDW;Mj zzhDMl4yBE)K%R}Ki(?4K_2dXv79L(69v_FMY{3Zu9!!E>4oRF75~8lMsDv@3?i6F- r)Sa+PR3V9RLIzXjYlhdXLJ5l*cy9_#pW5hq9%Q&ZX<&tKRm*z(OjWro6u3kOeL_&0rF`_x1({lov(^KiQy^q7?R e-+h79f+U94N`ZcV`^~38CVIO1xvX&ZX<&tKRm*z!w4QmyHtk6T;iG#=p?#}%J7ougF)#F`qq7#$ON U_r^>Mc>^-Z)78&qol`;+03)U%`2YX_ diff --git a/icons/ui/pda/pda_droneblacklist.png b/icons/ui/pda/pda_droneblacklist.png deleted file mode 100644 index 98fd4a444ef65151f14f69d94800d0da5a935515..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|u0R?Bnv-810J0cMg8YIR z9G=}s19I#&ZX<&tKR$ap8aE!z_MnPyHpsI*fcirDW8fxS)8#AXC+&;9^i4 i>l&Wl|Cy#TiZU~-+#|SihOq8Qkg=YwelF{r5}E+0?kau& diff --git a/icons/ui/pda/pda_eject.png b/icons/ui/pda/pda_eject.png deleted file mode 100644 index a9323f8f9f78de8aa002c0ff82ea893dfe7dda58..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9>wyCYfK2V{3zq>Y#*!ev zUp0yqCpOk`3&{AD^%u!-cdEF}+x cU;pJ9re+Dm`^g4P0%~ONboFyt=akR{0I@&ZX<&tKRm*zzpGNau5E#FJAS){7mv`+s^OllI}3khZ@0Ja6R{ bvl_Y>Ths(r3tzd-0y59j)z4*}Q$iB}p?)a4 diff --git a/icons/ui/pda/pda_exit.png b/icons/ui/pda/pda_exit.png deleted file mode 100644 index 68ef0c9e06d35572bfa6ee552596dd336a157809..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE;~K7sn8e>&ZX<&tKR$F_BsSFkfVw)VPb6za$t2x abvVi}S42>`m4Wjc$UIM1KbLh*2~7a1X(9js diff --git a/icons/ui/pda/pda_flashlight.png b/icons/ui/pda/pda_flashlight.png deleted file mode 100644 index e0a6042c48be519ed102dee873745c53e5c3b974..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE*Q17sn8e>&ZX<&tKRm*cKrv6=(G3{)r7)wi8%39aQQmUtDnm{r-UW|c_bjz diff --git a/icons/ui/pda/pda_floorbot.png b/icons/ui/pda/pda_floorbot.png deleted file mode 100644 index 48cd618f008c0b7f2151d17db8e9d5002be2ff9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE*o97sn8e>&ZX<&tKRm*d@hpv?bE0B-7~3|C1XWr#);k;W+#M#D#?%feax$ Y3?sdH?_b diff --git a/icons/ui/pda/pda_font.png b/icons/ui/pda/pda_font.png deleted file mode 100644 index 5561bd973ba7220e636f8ff168671fe5d677ea5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9JOMr-u0R?H*w`5O`57LVRNVw} z7)yfuf*Bm1-ADs+Y&=~YLpZJ{bFeb8v9qzcv9Yf;bx_LIQf{8iyjZz8nR!Bi)d7|i u-(Dsm2Qjvb3@U$wZZIpdwX{8JV`5nSLeOx}wi|arR(QJlxvX_ diff --git a/icons/ui/pda/pda_honk.png b/icons/ui/pda/pda_honk.png deleted file mode 100644 index 9e94b9d02bb43e60fb1d94d9214cd53d7c476beb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE-m%7sn8e>&ZX<&tKRm*j6F=ti$L{{izN8qCQmdKI;Vst0KIV`)&Kwi diff --git a/icons/ui/pda/pda_locked.PNG b/icons/ui/pda/pda_locked.PNG deleted file mode 100644 index 571ba4317ae1ed15916c2fe223eb3fa3459da0f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE;a47sn8e>&ZX<&tKR$ap6w|=ikn4`<&Y3oCRk67jAhL$|N}D?tj4*JS!v^ XE}s`M(zw+89b}fLtDnm{r-UW|-p?lA diff --git a/icons/ui/pda/pda_mail.png b/icons/ui/pda/pda_mail.png deleted file mode 100644 index 6904faec185426c2c315ec4b2d97aa4849cfb01c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPHV5AX?b1=0--Py}Qj5}2b4q*zLV z{DK)Gr3F&zS40E(#-1*YAsp9}3mTc&*x3vc4jyaJU^J4E;Av!XU<){Lko8PLLQ;XF kFe4KS^FrBv(SmdahMbN30a9Yk@<8njp00i_>zopr0FZejVgLXD diff --git a/icons/ui/pda/pda_medbot.png b/icons/ui/pda/pda_medbot.png deleted file mode 100644 index 4c7f178dd3418a509c394959bb241995d81133da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE*J~7sn8e>&ZX<&tEuyVq%)|;TE^HZ~Z*7>XOg?r_4||X%ILm@sp1NXVQ80 gE^Q+d113>shMnsKms-SL&;=Rl>FVdQ&MBb@00uoLTL1t6 diff --git a/icons/ui/pda/pda_medical.png b/icons/ui/pda/pda_medical.png deleted file mode 100644 index 5a09e3e1369a9c5b3a2f35376472b538a50f8518..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE*c57sn8e>&ZX<&tKR$ap6w|rnmpO-A_DnNOd^MAh1%5e&ZX<&tKR$G4Y>&8|xaL*ZYmu2&6{TiuyIVh^;VM6U7z65W*J1 Z!*Fh)aJuG3%|#&7JYD@<);T3K0RYHUBjNx6 diff --git a/icons/ui/pda/pda_mule.png b/icons/ui/pda/pda_mule.png deleted file mode 100644 index 8218646c86dcd28ed373f6ffe6aff7e6342c35e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE;~K7sn8e>&ZX<&tKRm*z$|tsKkQFQ=t04dmHN<9_|RqZ~s$gD4yK#M^k`N b(%~q>tS3UUr(%3Bg3R-D^>bP0l+XkK5TqsJ diff --git a/icons/ui/pda/pda_notes.png b/icons/ui/pda/pda_notes.png deleted file mode 100644 index 79ac48df10ffa1b4b8cbe19a9b61ed066760eb33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 126 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE+bX7sn8e>&ZX<&tKR$ap6y8=WYe(=dNv>O+1$`dJ4??FSvqdg#^Q*Ktccd SxoMpsV?15`T-G@yGywqYO(O#U diff --git a/icons/ui/pda/pda_power.png b/icons/ui/pda/pda_power.png deleted file mode 100644 index 1326a06259fe88f8e48f4211476eca052172a770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE*Q17sn8e>&ZX<&tKR$ap6h9#$WbEB^*rwzn$BTxmhr2F>A3gREY}c2I+*X P2N~k&>gTe~DWM4faD^Yb diff --git a/icons/ui/pda/pda_rdoor.png b/icons/ui/pda/pda_rdoor.png deleted file mode 100644 index ea0dbd96c47eda9f7e61b42c6bf1738b80d0dace..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE;B{7sn8e>&ZX<&tKR$ap8Yu=XM3IS7IqIS`74zN?4r)0t^|VH}P$M&091R OWQM1!pUXO@geCx32Ox+5 diff --git a/icons/ui/pda/pda_reagent.png b/icons/ui/pda/pda_reagent.png deleted file mode 100644 index 5baa932ff8db64453610ef232aeff250269c889c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE-m%7sn8e>&ZX<&tKRm*iv@F=u3Ud3xSju`#Kb?9aREk7O}06U}!xp;M3#1 QA|7Olr>mdKI;Vst0KmB-MF0Q* diff --git a/icons/ui/pda/pda_refresh.png b/icons/ui/pda/pda_refresh.png deleted file mode 100644 index a2d04df60f77648bb55abd65ec41987a83dad5b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE-;<7sn8e>&ZX<&tKR$aiL&`l;kaWqc3?WFGNyiC~`W<3OB|qQ1DpA*dfu_ Y#n=-q@LVd_NgiaHr>mdKI;Vst0IDh@P5=M^ diff --git a/icons/ui/pda/pda_scanner.png b/icons/ui/pda/pda_scanner.png deleted file mode 100644 index 9f1c71fc20bea2214efe4856a138ee3744037d4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!93?!50ihlx9oB=)|u0R?Bnv-810J4}$g8YIR zzA5?65(M&`JY5_^IIbs0u(I&*^6>cZ@W!leU||;)cD}6Crq-sWCa`cL*NL!&ZX<&tJHI(!%D+3#}(F^q#yhTh;lnSKB|ow!WD>vAsO9y#br{ fPh8k2xI%(q$v?sF2Dxe5Kt_7H`njxgN@xNACkHBA diff --git a/icons/ui/pda/pda_skills.png b/icons/ui/pda/pda_skills.png deleted file mode 100644 index 25096c7a82c42e243c5628074ff2e32a25e0b9c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE;yC7sn8e>&ZX<&tKR$F_BSuN(_t39G=w;9>P6gOoARY|HBtBEl6SrKE|K& Scg3G_kU5^NelF{r5}E+cLL!y` diff --git a/icons/ui/pda/pda_status.png b/icons/ui/pda/pda_status.png deleted file mode 100644 index 994dd76d528cc55a4521061b93120d4d4048e71e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 133 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!D3?x-;bCrM;TYyi9E06|)rRh7*11ZLmAirP+ zhi5m^fE-;<7sn8e>&ZX<&tKRm*z)XuO2mOv8@}ruZd=Jy{me**aZ+%jl0)GF9TrAW aW`^x8g8KqnXKe&(WbkzLb6Mw<&;$S|cqVB8 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/