diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 03d42b2638..370a5c01fb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -113,7 +113,7 @@ jobs:
run: |
sudo dpkg --add-architecture i386
sudo apt update || true
- sudo apt install zlib1g-dev:i386 libssl-dev:i386
+ sudo apt install zlib1g-dev:i386
ldd librust_g.so
ldd libbapi_dmm_reader.so
ldd libverdigris.so
diff --git a/Dockerfile b/Dockerfile
index e143dd1d9c..ccdf7188d5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,79 +1,79 @@
-FROM i386/ubuntu:xenial as base
+FROM ubuntu:22.04 AS base
-ARG BYOND_MAJOR=514
-ARG BYOND_MINOR=1589
+RUN dpkg --add-architecture i386 \
+ && apt-get update \
+ && apt-get upgrade -y \
+ && apt-get dist-upgrade -y \
+ && apt-get install -y --no-install-recommends \
+ ca-certificates
-RUN apt-get update \
- && apt-get install -y \
- curl \
- unzip \
- make \
- libstdc++6 \
- && curl "https://www.byond.com/download/build/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip \
- && unzip byond.zip \
- && cd byond \
- && sed -i 's|install:|&\n\tmkdir -p $(MAN_DIR)/man6|' Makefile \
- && make install \
- && chmod 644 /usr/local/byond/man/man6/* \
- && apt-get purge -y --auto-remove curl unzip make \
- && cd .. \
- && rm -rf byond byond.zip /var/lib/apt/lists/*
-
-FROM base as rust_g
-
-RUN apt-get update \
- && apt-get install -y --no-install-recommends \
- git \
- ca-certificates
-
-WORKDIR /rust_g
+FROM base AS byond
+WORKDIR /byond
RUN apt-get install -y --no-install-recommends \
- libssl-dev \
- pkg-config \
- curl \
- gcc-multilib \
- && curl https://sh.rustup.rs -sSf | sh -s -- -y --default-host i686-unknown-linux-gnu \
- && git init \
- && git remote add origin https://github.com/VOREStation/rust-g
+ curl \
+ unzip \
+ make \
+ libstdc++6:i386
-COPY _build_dependencies.sh .
+COPY dependencies.sh .
-RUN /bin/bash -c "source _build_dependencies.sh \
- && git fetch --depth 1 origin \$RUST_G_VERSION" \
- && git checkout FETCH_HEAD \
- && ~/.cargo/bin/cargo build --release
+RUN . ./dependencies.sh \
+ && curl "http://www.byond.com/download/build/${BYOND_MAJOR}/${BYOND_MAJOR}.${BYOND_MINOR}_byond_linux.zip" -o byond.zip \
+ && unzip byond.zip \
+ && cd byond \
+ && sed -i 's|install:|&\n\tmkdir -p $(MAN_DIR)/man6|' Makefile \
+ && make install \
+ && chmod 644 /usr/local/byond/man/man6/* \
+ && apt-get purge -y --auto-remove curl unzip make \
+ && cd .. \
+ && rm -rf byond byond.zip
-FROM base as dm_base
+FROM byond AS build
WORKDIR /vorestation
-FROM dm_base as build
+RUN apt-get install -y --no-install-recommends \
+ curl
COPY . .
-RUN DreamMaker -max_errors 0 vorestation.dme
+RUN env TG_BOOTSTRAP_NODE_LINUX=1 tools/build/build
-FROM dm_base
+FROM base AS rust
+RUN apt-get install -y --no-install-recommends \
+ curl && \
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal \
+ && ~/.cargo/bin/rustup target add i686-unknown-linux-gnu
-EXPOSE 2303
+FROM rust AS rust_g
+WORKDIR /rust_g
-RUN apt-get update \
- && apt-get install -y --no-install-recommends software-properties-common \
- && add-apt-repository ppa:ubuntu-toolchain-r/test \
- && apt-get update \
- && apt-get upgrade -y \
- && apt-get dist-upgrade -y \
- && apt-get install -y --no-install-recommends \
- libmariadb2 \
- mariadb-client \
- libssl1.0.0 \
- && rm -rf /var/lib/apt/lists/* \
- && mkdir -p /root/.byond/bin
+RUN apt-get install -y --no-install-recommends \
+ libssl3 \
+ gcc-multilib \
+ git \
+ && git init \
+ && git remote add origin https://github.com/tgstation/rust-g
+
+COPY dependencies.sh .
+
+RUN . ./dependencies.sh \
+ && git fetch --depth 1 origin "${RUST_G_VERSION}" \
+ && git checkout FETCH_HEAD \
+ && env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target i686-unknown-linux-gnu --features all
+
+FROM byond
+WORKDIR /vorestation
+
+RUN apt-get install -y --no-install-recommends \
+ libssl3 \
+ zlib1g:i386
COPY --from=build /vorestation/ ./
-COPY --from=rust_g /rust_g/target/release/librust_g.so ./librust_g.so
+COPY --from=rust_g /rust_g/target/i686-unknown-linux-gnu/release/librust_g.so ./librust_g.so
#VOLUME [ "/vorestation/config", "/vorestation/data" ]
ENTRYPOINT [ "DreamDaemon", "vorestation.dmb", "-port", "2303", "-trusted", "-close", "-verbose" ]
+EXPOSE 2303
diff --git a/_build_dependencies.sh b/_build_dependencies.sh
deleted file mode 100644
index 7902bb3ca5..0000000000
--- a/_build_dependencies.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file has all the information on what versions of libraries are thrown into the code
-# For dreamchecker
-export SPACEMAN_DMM_VERSION=suite-1.8
-# For NanoUI + TGUI
-export NODE_VERSION=20
-# Byond Major
-export BYOND_MAJOR=515
-# Byond Minor
-export BYOND_MINOR=1642
-# Macro Count
-export MACRO_COUNT=9 # YW EDIT FIXTHIS
diff --git a/code/__defines/assets.dm b/code/__defines/assets.dm
new file mode 100644
index 0000000000..bec54e1e54
--- /dev/null
+++ b/code/__defines/assets.dm
@@ -0,0 +1,16 @@
+#define ASSET_CROSS_ROUND_CACHE_DIRECTORY "data/spritesheets/legacy_cache"
+#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/__defines/is_helpers.dm b/code/__defines/is_helpers.dm
index fff94f906e..f56b590be9 100644
--- a/code/__defines/is_helpers.dm
+++ b/code/__defines/is_helpers.dm
@@ -71,3 +71,6 @@
// Diveable water
#define isdiveablewater(A) istype(A, /turf/simulated/floor/water/deep/ocean/diving)
+
+/// NaN isn't a number, damn it. Infinity is a problem too.
+#define isnum_safe(x) ( isnum((x)) && !isnan((x)) && !isinf((x)) )
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index b6b6cffd65..790d8dcf4a 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -379,6 +379,14 @@
#define STRUCTURE_MIN_DAMAGE_THRESHOLD 5
//Vision flags, for dealing with plane visibility
+/// AS A SIDE NOTE, PLEASE PLEASE PLEASE PLEASE PLEASE PLEAS PLEASE PLEASE PLEASE for the LOVE OF ALL THAT IS HOLY
+/// IF YOU ARE ADDING MORE VISION PLANES, ADD THEM IN HERE. DO NOT ADD THEM TO A _VR FILE. DO NOT ADD THEM TO A _CH FILE. DON'T ADD THEM TO A _VFX FILE.
+/// ADD. THEM. HERE. YES, YOU CAN OVERRIDE THE VIS_COUNT IN ANOTHER FILE AND ADD A NEW DEFINE THERE AS WELL, BUT IT WILL LEAD TO
+/// HEADACHES AND SUFFERING LATER DOWN THE LINE WHEN SOMEONE ADDS A NEW VISION LAYER HERE AND IT CONFLICTS WITH ONE IN ANOTHER FILE
+/// AND EVERYONE SCRATCHES THEIR HEAD AND GOES "WHY IS THIS SUDDENLY BROKE"
+/// AND THEN SOMEONE HAD TO DIG THROUGH VISION, PLANE, AND LAYER CODE. THEY WILL NOT BE HAPPY.
+/// SO PLEASE, ADD THEM H E R E
+/// Thank you :)
#define VIS_FULLBRIGHT 1
#define VIS_LIGHTING 2
#define VIS_O_LIGHT 3
@@ -416,7 +424,16 @@
#define VIS_STATUS 28
-#define VIS_COUNT 28 //Must be highest number from above.
+#define VIS_CH_STATUS_R 29
+#define VIS_CH_HEALTH_VR 30
+#define VIS_CH_BACKUP 31
+#define VIS_CH_VANTAG 32
+
+#define VIS_AUGMENTED 33
+
+#define VIS_CH_STOMACH 34
+
+#define VIS_COUNT 34 //Must be highest number from above.
//Some mob icon layering defines
#define BODY_LAYER -100
diff --git a/code/__defines/mobs_vr.dm b/code/__defines/mobs_vr.dm
index 1d6ecb049d..af0eddf426 100644
--- a/code/__defines/mobs_vr.dm
+++ b/code/__defines/mobs_vr.dm
@@ -1,16 +1,3 @@
-#undef VIS_COUNT
-
-#define VIS_CH_STATUS_R 28
-#define VIS_CH_HEALTH_VR 29
-#define VIS_CH_BACKUP 30
-#define VIS_CH_VANTAG 31
-
-#define VIS_AUGMENTED 32
-
-#define VIS_CH_STOMACH 33
-
-#define VIS_COUNT 33
-
//Protean organs
#define O_ORCH "orchestrator"
#define O_FACT "refactory"
diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm
index b71b20e037..babe0b58d2 100644
--- a/code/_helpers/_lists.dm
+++ b/code/_helpers/_lists.dm
@@ -960,3 +960,18 @@ var/global/list/json_cache = list()
return_list += bit
return return_list
+
+/// 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_safe(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
diff --git a/code/controllers/configuration/entries/resources.dm b/code/controllers/configuration/entries/resources.dm
index 475cb8ca70..8623f4d75c 100644
--- a/code/controllers/configuration/entries/resources.dm
+++ b/code/controllers/configuration/entries/resources.dm
@@ -8,4 +8,6 @@
/datum/config_entry/flag/cache_assets
+/datum/config_entry/flag/smart_cache_assets
+
/datum/config_entry/flag/save_spritesheets
diff --git a/code/controllers/subsystems/asset_loading.dm b/code/controllers/subsystems/asset_loading.dm
index 2d2939de4b..ecdf406894 100644
--- a/code/controllers/subsystems/asset_loading.dm
+++ b/code/controllers/subsystems/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/modules/admin/admin_verb_lists.dm b/code/modules/admin/admin_verb_lists.dm
index edb02c8e65..b4d6f2fd49 100644
--- a/code/modules/admin/admin_verb_lists.dm
+++ b/code/modules/admin/admin_verb_lists.dm
@@ -237,6 +237,8 @@ var/list/admin_verbs_debug = list(
/client/proc/show_gm_status,
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
+ /client/proc/cmd_regenerate_asset_cache,
+ /client/proc/cmd_clear_smart_asset_cache,
/client/proc/admin_give_modifier,
/client/proc/simple_DPS,
/datum/admins/proc/view_feedback,
@@ -391,6 +393,8 @@ var/list/admin_verbs_event_manager = list(
/proc/release,
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
+ /client/proc/cmd_regenerate_asset_cache,
+ /client/proc/cmd_clear_smart_asset_cache,
/client/proc/admin_give_modifier,
/client/proc/Jump,
/client/proc/jumptomob,
@@ -418,6 +422,8 @@ var/list/admin_verbs_event_manager = list(
/client/proc/show_gm_status,
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
+ /client/proc/cmd_regenerate_asset_cache,
+ /client/proc/cmd_clear_smart_asset_cache,
/client/proc/admin_give_modifier,
/datum/admins/proc/cmd_admin_dress,
/client/proc/cmd_admin_gib_self,
diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm
index 6883e0d32f..52a8cc3cb4 100644
--- a/code/modules/admin/admin_verb_lists_vr.dm
+++ b/code/modules/admin/admin_verb_lists_vr.dm
@@ -283,6 +283,8 @@ var/list/admin_verbs_debug = list(
/datum/admins/proc/set_uplink, //VOREStation Add,
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
+ /client/proc/cmd_regenerate_asset_cache,
+ /client/proc/cmd_clear_smart_asset_cache,
/client/proc/admin_give_modifier,
/client/proc/simple_DPS,
/datum/admins/proc/view_feedback,
@@ -455,6 +457,8 @@ var/list/admin_verbs_event_manager = list(
/proc/release,
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
+ /client/proc/cmd_regenerate_asset_cache,
+ /client/proc/cmd_clear_smart_asset_cache,
/client/proc/admin_give_modifier,
/client/proc/Jump,
/client/proc/jumptomob,
@@ -482,6 +486,8 @@ var/list/admin_verbs_event_manager = list(
// /client/proc/show_gm_status, // VOREStation Edit - We don't use SSgame_master yet.
/datum/admins/proc/change_weather,
/datum/admins/proc/change_time,
+ /client/proc/cmd_regenerate_asset_cache,
+ /client/proc/cmd_clear_smart_asset_cache,
/client/proc/admin_give_modifier,
/datum/admins/proc/cmd_admin_dress,
/client/proc/cmd_admin_gib_self,
diff --git a/code/modules/admin/player_effects.dm b/code/modules/admin/player_effects.dm
index 63cf456148..0931c53988 100644
--- a/code/modules/admin/player_effects.dm
+++ b/code/modules/admin/player_effects.dm
@@ -614,6 +614,12 @@
add_verb(Tar, /mob/living/proc/eat_trash)
add_verb(Tar, /mob/living/proc/toggle_trash_catching)
+ if("active_cloaking")
+ var/mob/living/Tar = target
+ if(!istype(Tar))
+ return
+ add_verb(Tar, /mob/living/proc/toggle_active_cloaking)
+
////////INVENTORY//////////////
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 180062c1b3..cac043768a 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -695,3 +695,36 @@
var/log = "[key_name(src)] changed [planet.name]'s time to [planet.current_time.show_time("hh:mm")]."
message_admins(log)
log_admin(log)
+
+/client/proc/cmd_regenerate_asset_cache()
+ set category = "Debug.Assets"
+ set name = "Regenerate Asset Cache"
+ set desc = "Clears the asset cache and regenerates it immediately."
+ if(!CONFIG_GET(flag/cache_assets))
+ to_chat(usr, span_warning("Asset caching is disabled in the config!"))
+ return
+ var/regenerated = 0
+ for(var/datum/asset/A as() in subtypesof(/datum/asset))
+ if(!initial(A.cross_round_cachable))
+ continue
+ if(A == initial(A._abstract))
+ continue
+ var/datum/asset/asset_datum = GLOB.asset_datums[A]
+ asset_datum.regenerate()
+ regenerated++
+ to_chat(usr, span_notice("Regenerated [regenerated] asset\s."))
+
+/client/proc/cmd_clear_smart_asset_cache()
+ set category = "Debug.Assets"
+ set name = "Clear Smart Asset Cache"
+ set desc = "Clears the smart asset cache."
+ if(!CONFIG_GET(flag/smart_cache_assets))
+ to_chat(usr, span_warning("Smart asset caching is disabled in the config!"))
+ return
+ var/cleared = 0
+ for(var/datum/asset/spritesheet_batched/A as() in subtypesof(/datum/asset/spritesheet_batched))
+ if(A == initial(A._abstract))
+ continue
+ fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.[initial(A.name)].json")
+ cleared++
+ to_chat(usr, span_notice("Cleared [cleared] asset\s."))
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index a21d95c14e..de3e6da4af 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -411,14 +411,18 @@
else if(href_list["addverb"])
if(!check_rights(R_DEBUG)) return
- var/mob/living/H = locate(href_list["addverb"])
+ var/mob/H = locate(href_list["addverb"])
- if(!istype(H))
- to_chat(usr, "This can only be done to instances of type /mob/living")
+ if(!ismob(H))
+ to_chat(usr, "This can only be done to instances of type /mob")
return
var/list/possibleverbs = list()
possibleverbs += "Cancel" // One for the top...
- possibleverbs += typesof(/mob/proc,/mob/verb,/mob/living/proc,/mob/living/verb)
+ possibleverbs += typesof(/mob/proc, /mob/verb)
+ if(istype(H,/mob/observer/dead))
+ possibleverbs += typesof(/mob/observer/dead/proc,/mob/observer/dead/verb)
+ if(istype(H,/mob/living))
+ possibleverbs += typesof(/mob/living/proc,/mob/living/verb)
if(istype(H,/mob/living/carbon/human))
possibleverbs += typesof(/mob/living/carbon/proc,/mob/living/carbon/verb,/mob/living/carbon/human/verb,/mob/living/carbon/human/proc)
if(istype(H,/mob/living/silicon/robot))
diff --git a/code/modules/asset_cache/asset_cache_item.dm b/code/modules/asset_cache/asset_cache_item.dm
index ad4596bded..dbee71b525 100644
--- a/code/modules/asset_cache/asset_cache_item.dm
+++ b/code/modules/asset_cache/asset_cache_item.dm
@@ -28,22 +28,28 @@
///pass in a valid file_hash if you have one to save it from needing to do it again.
///pass in a valid dmi file path string e.g. "icons/path/to/dmi_file.dmi" to make generating the hash less expensive
/datum/asset_cache_item/New(name, file, file_hash, dmi_file_path)
- if (!isfile(file))
+ if(!isfile(file))
file = fcopy_rsc(file)
- hash = file_hash
+ if(length(file) == 0)
+ log_asset("WARNING: [name] is an empty file, this is almost certainly not intended and could indicate a bad boot!")
+ message_admins("ASSET WARNING: [name] is an empty file, this is almost certainly not intended and could indicate a bad boot!")
+ hash = file_hash
//the given file is directly from a dmi file and is thus in the rsc already, we know that its file_hash will be correct
if(!hash)
if(dmi_file_path)
hash = md5(file)
else
hash = md5asfile(file) //icons sent to the rsc md5 incorrectly when theyre given incorrect data
- if (!hash)
- CRASH("invalid asset sent to asset cache")
+ if(!hash)
+ hash = md5(fcopy_rsc(file))
+ if (!hash)
+ CRASH("invalid asset sent to asset cache")
+ log_debug("asset cache unexpected success of second fcopy_rsc")
src.name = name
var/extstart = findlasttext(name, ".")
- if (extstart)
+ if(extstart)
ext = ".[copytext(name, extstart+1)]"
resource = file
diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm
index bdc062e066..d3c75b77e6 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
@@ -20,15 +18,15 @@ GLOBAL_LIST_EMPTY(asset_datums)
var/cached_serialized_url_mappings
var/cached_serialized_url_mappings_transport_type
- /// Whether or not this asset should be loaded in the "early assets" SS
- var/early = FALSE
-
/// Whether or not this asset can be cached across rounds of the same commit under the `CACHE_ASSETS` config.
/// This is not a *guarantee* the asset will be cached. Not all asset subtypes respect this field, and the
/// config can, of course, be disabled.
/// Disable this if your asset can change between rounds on the same exact version of the code.
var/cross_round_cachable = FALSE
+ /// Whether or not this asset should be loaded in the "early assets" SS
+ var/early = FALSE
+
/datum/asset/New()
GLOB.asset_datums[type] = src
register()
@@ -63,6 +61,19 @@ 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()
+ SHOULD_CALL_PARENT(FALSE)
+ unregister()
+ cached_serialized_url_mappings = null
+ cached_serialized_url_mappings_transport_type = null
+ register()
+
+/// Unregisters any assets from the transport.
+/datum/asset/proc/unregister()
+ SHOULD_CALL_PARENT(FALSE)
+ 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)
@@ -86,12 +97,12 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/simple/register()
for(var/asset_name in assets)
var/datum/asset_cache_item/ACI = SSassets.transport.register_asset(asset_name, assets[asset_name])
- if (!ACI)
+ if(!istype(ACI))
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
- if (legacy)
+ if(legacy)
ACI.legacy = legacy
- if (keep_local_name)
+ if(keep_local_name)
ACI.keep_local_name = keep_local_name
assets[asset_name] = ACI
@@ -103,6 +114,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
for (var/asset_name in assets)
.[asset_name] = SSassets.transport.get_asset_url(asset_name, assets[asset_name])
+
// For registering or sending multiple others at once
/datum/asset/group
_abstract = /datum/asset/group
@@ -123,6 +135,11 @@ GLOBAL_LIST_EMPTY(asset_datums)
var/datum/asset/A = get_asset_datum(type)
. += A.get_url_mappings()
+/datum/asset/group/unregister()
+ for(var/type in children)
+ var/datum/asset/A = get_asset_datum(type)
+ A.unregister()
+
// 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
@@ -147,9 +164,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
/// 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
+ /// Allows resizing all icons it comes across by a multiplier (32x32 * 2 = 64x64)
+ var/resize = 1
+ /// If this asset should CRASH or ignore when duplicate sprite keys are added
+ var/duplicates_allowed = FALSE
/datum/asset/spritesheet/proc/should_load_immediately()
#ifdef DO_NOT_DEFER_ASSETS
@@ -158,28 +176,58 @@ GLOBAL_LIST_EMPTY(asset_datums)
return load_immediately
#endif
-
/datum/asset/spritesheet/should_refresh()
if (..())
return TRUE
+ // Static so that the result is the same, even when the files are created, for this run
+ var/static/should_refresh = null
+
if (isnull(should_refresh))
// `fexists` seems to always fail on static-time
- should_refresh = !fexists(css_cache_filename()) || !fexists(data_cache_filename())
+ should_refresh = !fexists("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css")
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)
+ if(!name)
CRASH("spritesheet [type] cannot register without a name")
- if (!should_refresh() && read_from_cache())
+ 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
@@ -202,17 +250,19 @@ GLOBAL_LIST_EMPTY(asset_datums)
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))
-
+ var/file_path = size[SPRSZ_STRIPPED]
+ var/file_hash = rustg_hash_file("md5", file_path)
+ SSassets.transport.register_asset("[name]_[size_id].png", file_path, file_hash=file_hash)
+ var/res_name = "spritesheet_[name].css"
+ var/fname = "data/spritesheets/[res_name]"
+ fdel(fname)
+ var/css = generate_css()
+ rustg_file_write(css, fname)
+ var/css_hash = rustg_hash_string("md5", css)
+ SSassets.transport.register_asset(res_name, fcopy_rsc(fname), file_hash=css_hash)
if(CONFIG_GET(flag/save_spritesheets))
- save_to_logs(file_name = css_name, file_location = file_directory)
-
- fdel(file_directory)
+ save_to_logs(file_name = res_name, file_location = fname)
+ fdel(fname)
if (CONFIG_GET(flag/cache_assets) && cross_round_cachable)
write_to_cache()
@@ -229,10 +279,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
return ..()
/datum/asset/spritesheet/send(client/client)
- if (!name)
+ if(!name)
return
- if (!should_refresh())
+ if(!should_refresh())
return send_from_cache(client)
var/all = list("spritesheet_[name].css")
@@ -254,33 +304,46 @@ GLOBAL_LIST_EMPTY(asset_datums)
/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])
+ 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)
+ var/fname = "data/spritesheets/[name]_[size_id].png"
+ fcopy(size[SPRSZ_ICON], fname)
+ var/error = rustg_dmi_strip_metadata(fname)
if(length(error))
- stack_trace("Failed to strip [png_name]: [error]")
- size[SPRSZ_STRIPPED] = icon(file_directory)
+ stack_trace("Failed to strip [name]_[size_id].png: [error]")
+
+ // Difference from Beestation: Resizing handling
+ var/icon/stripped = icon(fname)
+ if(resize != 1)
+ var/new_width = round(stripped.Width() * resize)
+ var/new_height = round(stripped.Height() * resize)
+ // Note: arguments MUST be strings or they don't make it past ffi
+ var/error_two = rustg_dmi_resize_png(fname, "[new_width]", "[new_height]", "nearest")
+ if(error_two)
+ stack_trace("Failed to resize [name]_[size_id].png to [new_width]x[new_height]: [error_two]")
+
+ size[SPRSZ_STRIPPED] = icon(fname)
+ else
+ size[SPRSZ_STRIPPED] = stripped
// 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)
+ save_to_logs(file_name = "[name]_[size_id].png", file_location = fname)
- fdel(file_directory)
+ fdel(fname)
/datum/asset/spritesheet/proc/generate_css()
var/list/out = list()
- for (var/size_id in sizes)
+ 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;}"
+ // Difference from Beestation: * resize on width and height
+ out += ".[name][size_id]{display:inline-block;width:[round(tiny.Width() * resize)]px;height:[round(tiny.Height() * resize)]px;background:url('[get_background_url("[name]_[size_id].png")]') no-repeat;}"
- for (var/sprite_id in sprites)
+ for(var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
var/size_id = sprite[SPR_SIZE]
var/idx = sprite[SPR_IDX]
@@ -288,9 +351,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
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()
+ // Difference from Beestation: Resizing handling
+ var/per_line = big.Width() / round(tiny.Width() * resize)
+ var/x = (idx % per_line) * round(tiny.Width() * resize)
+ var/y = round(idx / per_line) * round(tiny.Height() * resize)
out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
@@ -303,39 +367,31 @@ GLOBAL_LIST_EMPTY(asset_datums)
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/replaced_css = rustg_file_read("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css")
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/file_path = "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[asset_id]"
+ // Hashing it here is a *lot* faster.
+ var/hash = rustg_hash_file("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:url('[asset_url]')")
LAZYADD(cached_spritesheets_needed, asset_id)
- var/finalized_name = "spritesheet_[name].css"
- var/replaced_css_filename = "data/spritesheets/[finalized_name]"
+ var/replaced_css_filename = "data/spritesheets/spritesheet_[name].css"
+ var/css_hash = rustg_hash_string("md5", replaced_css)
rustg_file_write(replaced_css, replaced_css_filename)
- SSassets.transport.register_asset(finalized_name, replaced_css_filename)
+ SSassets.transport.register_asset("spritesheet_[name].css", 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)
+ save_to_logs(file_name = "spritesheet_[name].css", 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")
@@ -351,23 +407,15 @@ GLOBAL_LIST_EMPTY(asset_datums)
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")
+ var/datum/asset_cache_item/temp = SSassets.cache["[name]_[size_id].png"]
+ fcopy(temp.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())
+ rustg_file_write(mock_css, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css")
/datum/asset/spritesheet/proc/get_cached_url_mappings()
var/list/mappings = list()
@@ -392,20 +440,17 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/spritesheet/proc/queuedInsert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
- if (!I || !length(icon_states(I))) // that direction or state doesn't exist
+ 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])
+ if(sprites[sprite_name])
+ if(duplicates_allowed)
+ return // No crash
CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
- if (size)
+ 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
@@ -424,26 +469,15 @@ GLOBAL_LIST_EMPTY(asset_datums)
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))
+ if(length(prefix))
prefix = "[prefix]-"
- if (!directions)
+ if(!directions)
directions = list(SOUTH)
- for (var/icon_state_name in icon_states(I))
- for (var/direction in directions)
+ 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)
@@ -455,10 +489,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/spritesheet/proc/icon_tag(sprite_name)
var/sprite = sprites[sprite_name]
- if (!sprite)
+ if(!sprite)
return null
var/size_id = sprite[SPR_SIZE]
- return {""}
+ return {""}
/datum/asset/spritesheet/proc/icon_class_name(sprite_name)
var/sprite = sprites[sprite_name]
@@ -486,6 +520,13 @@ GLOBAL_LIST_EMPTY(asset_datums)
#undef SPRSZ_ICON
#undef SPRSZ_STRIPPED
+/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
_abstract = /datum/asset/changelog_item
@@ -505,14 +546,6 @@ 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])
-
//Generates assets based on iconstates of a single icon
/datum/asset/simple/icon_states
_abstract = /datum/asset/simple/icon_states
@@ -528,7 +561,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
for(var/icon_state_name in icon_states(_icon))
for(var/direction in directions)
var/asset = icon(_icon, icon_state_name, direction, frame, movement_states)
- if (!asset)
+ if(!asset)
continue
asset = fcopy_rsc(asset) //dedupe
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : ""
@@ -558,36 +591,37 @@ GLOBAL_LIST_EMPTY(asset_datums)
var/list/parents = list()
/datum/asset/simple/namespaced/register()
- if (legacy)
+ if(legacy)
assets |= parents
var/list/hashlist = list()
- var/list/sorted_assets = sortList(assets)
+ var/list/created_items = list()
- for (var/asset_name in sorted_assets)
+ var/list/sorted_assets = sortList(assets)
+ for(var/asset_name in sorted_assets)
var/datum/asset_cache_item/ACI = new(asset_name, sorted_assets[asset_name])
- if (!ACI?.hash)
+ if (!istype(ACI) || !ACI.hash)
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
hashlist += ACI.hash
- sorted_assets[asset_name] = ACI
+ created_items[asset_name] = ACI
var/namespace = md5(hashlist.Join())
- for (var/asset_name in parents)
+ for(var/asset_name in parents)
var/datum/asset_cache_item/ACI = new(asset_name, parents[asset_name])
- if (!ACI?.hash)
+ if (!istype(ACI) || !ACI.hash)
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
ACI.namespace_parent = TRUE
- sorted_assets[asset_name] = ACI
+ created_items[asset_name] = ACI
- for (var/asset_name in sorted_assets)
- var/datum/asset_cache_item/ACI = sorted_assets[asset_name]
- if (!ACI?.hash)
+ for(var/asset_name in created_items)
+ var/datum/asset_cache_item/ACI = created_items[asset_name]
+ if (!istype(ACI) || !ACI.hash)
log_asset("ERROR: Invalid asset: [type]:[asset_name]:[ACI]")
continue
ACI.namespace = namespace
- assets = sorted_assets
+ assets = created_items
..()
/// Get a html string that will load a html asset.
@@ -612,7 +646,7 @@ 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)
@@ -621,4 +655,5 @@ GLOBAL_LIST_EMPTY(asset_datums)
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/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm
deleted file mode 100644
index 59bd2b040e..0000000000
--- a/code/modules/asset_cache/asset_list_items.dm
+++ /dev/null
@@ -1,544 +0,0 @@
-//DEFINITIONS FOR ASSET DATUMS START HERE.
-/datum/asset/simple/headers
- assets = list(
- "alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
- "alarm_red.gif" = 'icons/program_icons/alarm_red.gif',
- "batt_5.gif" = 'icons/program_icons/batt_5.gif',
- "batt_20.gif" = 'icons/program_icons/batt_20.gif',
- "batt_40.gif" = 'icons/program_icons/batt_40.gif',
- "batt_60.gif" = 'icons/program_icons/batt_60.gif',
- "batt_80.gif" = 'icons/program_icons/batt_80.gif',
- "batt_100.gif" = 'icons/program_icons/batt_100.gif',
- "charging.gif" = 'icons/program_icons/charging.gif',
- "downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif',
- "downloader_running.gif" = 'icons/program_icons/downloader_running.gif',
- "ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif',
- "ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif',
- "power_norm.gif" = 'icons/program_icons/power_norm.gif',
- "power_warn.gif" = 'icons/program_icons/power_warn.gif',
- "sig_high.gif" = 'icons/program_icons/sig_high.gif',
- "sig_low.gif" = 'icons/program_icons/sig_low.gif',
- "sig_lan.gif" = 'icons/program_icons/sig_lan.gif',
- "sig_none.gif" = 'icons/program_icons/sig_none.gif',
- "smmon_0.gif" = 'icons/program_icons/smmon_0.gif',
- "smmon_1.gif" = 'icons/program_icons/smmon_1.gif',
- "smmon_2.gif" = 'icons/program_icons/smmon_2.gif',
- "smmon_3.gif" = 'icons/program_icons/smmon_3.gif',
- "smmon_4.gif" = 'icons/program_icons/smmon_4.gif',
- "smmon_5.gif" = 'icons/program_icons/smmon_5.gif',
- "smmon_6.gif" = 'icons/program_icons/smmon_6.gif',
- // "borg_mon.gif" = 'icons/program_icons/borg_mon.gif'
- )
-
-// /datum/asset/simple/radar_assets
-// assets = list(
-// "ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png',
-// "ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png',
-// "ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png'
-// )
-
-// /datum/asset/spritesheet/simple/pda
-// name = "pda"
-// assets = list(
-// "atmos" = 'icons/pda_icons/pda_atmos.png',
-// "back" = 'icons/pda_icons/pda_back.png',
-// "bell" = 'icons/pda_icons/pda_bell.png',
-// "blank" = 'icons/pda_icons/pda_blank.png',
-// "boom" = 'icons/pda_icons/pda_boom.png',
-// "bucket" = 'icons/pda_icons/pda_bucket.png',
-// "medbot" = 'icons/pda_icons/pda_medbot.png',
-// "floorbot" = 'icons/pda_icons/pda_floorbot.png',
-// "cleanbot" = 'icons/pda_icons/pda_cleanbot.png',
-// "crate" = 'icons/pda_icons/pda_crate.png',
-// "cuffs" = 'icons/pda_icons/pda_cuffs.png',
-// "eject" = 'icons/pda_icons/pda_eject.png',
-// "flashlight" = 'icons/pda_icons/pda_flashlight.png',
-// "honk" = 'icons/pda_icons/pda_honk.png',
-// "mail" = 'icons/pda_icons/pda_mail.png',
-// "medical" = 'icons/pda_icons/pda_medical.png',
-// "menu" = 'icons/pda_icons/pda_menu.png',
-// "mule" = 'icons/pda_icons/pda_mule.png',
-// "notes" = 'icons/pda_icons/pda_notes.png',
-// "power" = 'icons/pda_icons/pda_power.png',
-// "rdoor" = 'icons/pda_icons/pda_rdoor.png',
-// "reagent" = 'icons/pda_icons/pda_reagent.png',
-// "refresh" = 'icons/pda_icons/pda_refresh.png',
-// "scanner" = 'icons/pda_icons/pda_scanner.png',
-// "signaler" = 'icons/pda_icons/pda_signaler.png',
-// "skills" = 'icons/pda_icons/pda_skills.png',
-// "status" = 'icons/pda_icons/pda_status.png',
-// "dronephone" = 'icons/pda_icons/pda_dronephone.png',
-// "emoji" = 'icons/pda_icons/pda_emoji.png'
-// )
-
-// /datum/asset/spritesheet/simple/paper
-// name = "paper"
-// assets = list(
-// "stamp-clown" = 'icons/stamp_icons/large_stamp-clown.png',
-// "stamp-deny" = 'icons/stamp_icons/large_stamp-deny.png',
-// "stamp-ok" = 'icons/stamp_icons/large_stamp-ok.png',
-// "stamp-hop" = 'icons/stamp_icons/large_stamp-hop.png',
-// "stamp-cmo" = 'icons/stamp_icons/large_stamp-cmo.png',
-// "stamp-ce" = 'icons/stamp_icons/large_stamp-ce.png',
-// "stamp-hos" = 'icons/stamp_icons/large_stamp-hos.png',
-// "stamp-rd" = 'icons/stamp_icons/large_stamp-rd.png',
-// "stamp-cap" = 'icons/stamp_icons/large_stamp-cap.png',
-// "stamp-qm" = 'icons/stamp_icons/large_stamp-qm.png',
-// "stamp-law" = 'icons/stamp_icons/large_stamp-law.png',
-// "stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png',
-// "stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png',
-// "stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png',
-// "stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png'
-// )
-
-
-// /datum/asset/simple/irv
-// assets = list(
-// "jquery-ui.custom-core-widgit-mouse-sortable-min.js" = 'html/IRV/jquery-ui.custom-core-widgit-mouse-sortable-min.js',
-// )
-
-// /datum/asset/group/irv
-// children = list(
-// /datum/asset/simple/jquery,
-// /datum/asset/simple/irv
-// )
-
-/datum/asset/simple/generic
- assets = list(
- "search.js" = 'html/search.js',
- "panels.css" = 'html/panels.css',
- "loading.gif" = 'html/images/loading.gif',
- "ntlogo.png" = 'html/images/ntlogo.png',
- "sglogo.png" = 'html/images/sglogo.png',
- "talisman.png" = 'html/images/talisman.png',
- "paper_bg.png" = 'html/images/paper_bg.png',
- "no_image32.png" = 'html/images/no_image32.png',
- )
-
-/datum/asset/simple/changelog
- assets = list(
- "88x31.png" = 'html/88x31.png',
- "bug-minus.png" = 'html/bug-minus.png',
- "cross-circle.png" = 'html/cross-circle.png',
- "hard-hat-exclamation.png" = 'html/hard-hat-exclamation.png',
- "image-minus.png" = 'html/image-minus.png',
- "image-plus.png" = 'html/image-plus.png',
- "map-pencil.png" = 'html/map-pencil.png',
- "music-minus.png" = 'html/music-minus.png',
- "music-plus.png" = 'html/music-plus.png',
- "tick-circle.png" = 'html/tick-circle.png',
- "wrench-screwdriver.png" = 'html/wrench-screwdriver.png',
- "spell-check.png" = 'html/spell-check.png',
- "burn-exclamation.png" = 'html/burn-exclamation.png',
- "chevron.png" = 'html/chevron.png',
- "chevron-expand.png" = 'html/chevron-expand.png',
- "changelog.css" = 'html/changelog.css',
- "changelog.js" = 'html/changelog.js',
- "changelog.html" = 'html/changelog.html'
- )
-
-// /datum/asset/group/goonchat
-// children = list(
-// /datum/asset/simple/jquery,
-// /datum/asset/simple/goonchat,
-// /datum/asset/spritesheet/goonchat,
-// /datum/asset/simple/fontawesome
-// )
-
-// /datum/asset/simple/goonchat
-// assets = list(
-// "json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js',
-// "browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js',
-// "browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css',
-// "browserOutput_white.css" = 'code/modules/goonchat/browserassets/css/browserOutput_white.css',
-// )
-
-// /datum/asset/spritesheet/goonchat
-// name = "chat"
-
-// /datum/asset/spritesheet/goonchat/register()
-// InsertAll("emoji", 'icons/emoji.dmi')
-
-// // pre-loading all lanugage icons also helps to avoid meta
-// InsertAll("language", 'icons/misc/language.dmi')
-// // catch languages which are pulling icons from another file
-// for(var/path in typesof(/datum/language))
-// var/datum/language/L = path
-// var/icon = initial(L.icon)
-// if (icon != 'icons/misc/language.dmi')
-// var/icon_state = initial(L.icon_state)
-// Insert("language-[icon_state]", icon, icon_state=icon_state)
-
-// ..()
-
-// /datum/asset/simple/permissions
-// assets = list(
-// "padlock.png" = 'html/padlock.png'
-// )
-
-// /datum/asset/simple/notes
-// assets = list(
-// "high_button.png" = 'html/high_button.png',
-// "medium_button.png" = 'html/medium_button.png',
-// "minor_button.png" = 'html/minor_button.png',
-// "none_button.png" = 'html/none_button.png',
-// )
-
-/datum/asset/simple/arcade
- assets = list(
- "boss1.gif" = 'icons/UI_Icons/Arcade/boss1.gif',
- "boss2.gif" = 'icons/UI_Icons/Arcade/boss2.gif',
- "boss3.gif" = 'icons/UI_Icons/Arcade/boss3.gif',
- "boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif',
- "boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif',
- "boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif',
- )
-
-// /datum/asset/spritesheet/simple/achievements
-// name ="achievements"
-// assets = list(
-// "default" = 'icons/UI_Icons/Achievements/default.png',
-// "basemisc" = 'icons/UI_Icons/Achievements/basemisc.png',
-// "baseboss" = 'icons/UI_Icons/Achievements/baseboss.png',
-// "baseskill" = 'icons/UI_Icons/Achievements/baseskill.png',
-// "bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png',
-// "colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png',
-// "hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png',
-// "legion" = 'icons/UI_Icons/Achievements/Boss/legion.png',
-// "miner" = 'icons/UI_Icons/Achievements/Boss/miner.png',
-// "swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png',
-// "tendril" = 'icons/UI_Icons/Achievements/Boss/tendril.png',
-// "featofstrength" = 'icons/UI_Icons/Achievements/Misc/featofstrength.png',
-// "helbital" = 'icons/UI_Icons/Achievements/Misc/helbital.png',
-// "jackpot" = 'icons/UI_Icons/Achievements/Misc/jackpot.png',
-// "meteors" = 'icons/UI_Icons/Achievements/Misc/meteors.png',
-// "timewaste" = 'icons/UI_Icons/Achievements/Misc/timewaste.png',
-// "upgrade" = 'icons/UI_Icons/Achievements/Misc/upgrade.png',
-// "clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png',
-// "clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png',
-// "rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png',
-// "snail" = 'icons/UI_Icons/Achievements/Misc/snail.png',
-// "mining" = 'icons/UI_Icons/Achievements/Skills/mining.png',
-// )
-
-// /datum/asset/spritesheet/simple/pills
-// name ="pills"
-// assets = list(
-// "pill1" = 'icons/UI_Icons/Pills/pill1.png',
-// "pill2" = 'icons/UI_Icons/Pills/pill2.png',
-// "pill3" = 'icons/UI_Icons/Pills/pill3.png',
-// "pill4" = 'icons/UI_Icons/Pills/pill4.png',
-// "pill5" = 'icons/UI_Icons/Pills/pill5.png',
-// "pill6" = 'icons/UI_Icons/Pills/pill6.png',
-// "pill7" = 'icons/UI_Icons/Pills/pill7.png',
-// "pill8" = 'icons/UI_Icons/Pills/pill8.png',
-// "pill9" = 'icons/UI_Icons/Pills/pill9.png',
-// "pill10" = 'icons/UI_Icons/Pills/pill10.png',
-// "pill11" = 'icons/UI_Icons/Pills/pill11.png',
-// "pill12" = 'icons/UI_Icons/Pills/pill12.png',
-// "pill13" = 'icons/UI_Icons/Pills/pill13.png',
-// "pill14" = 'icons/UI_Icons/Pills/pill14.png',
-// "pill15" = 'icons/UI_Icons/Pills/pill15.png',
-// "pill16" = 'icons/UI_Icons/Pills/pill16.png',
-// "pill17" = 'icons/UI_Icons/Pills/pill17.png',
-// "pill18" = 'icons/UI_Icons/Pills/pill18.png',
-// "pill19" = 'icons/UI_Icons/Pills/pill19.png',
-// "pill20" = 'icons/UI_Icons/Pills/pill20.png',
-// "pill21" = 'icons/UI_Icons/Pills/pill21.png',
-// "pill22" = 'icons/UI_Icons/Pills/pill22.png',
-// )
-
-// //this exists purely to avoid meta by pre-loading all language icons.
-// /datum/asset/language/register()
-// for(var/path in typesof(/datum/language))
-// set waitfor = FALSE
-// var/datum/language/L = new path ()
-// L.get_icon()
-
-/datum/asset/spritesheet/pipes
- name = "pipes"
-
-/datum/asset/spritesheet/pipes/create_spritesheets()
- for(var/each in list('icons/obj/pipe-item.dmi', 'icons/obj/pipes/disposal.dmi'))
- InsertAll("", each, global.alldirs)
-
-//VOREStation Add
-/datum/asset/spritesheet/vore
- name = "vore"
-
-/datum/asset/spritesheet/vore/create_spritesheets()
- var/icon/downscaled = icon('icons/mob/screen_full_vore.dmi')
- downscaled.Scale(240, 240)
- InsertAll("", downscaled)
-
-/datum/asset/spritesheet/vore_colorized //This should be getting loaded in the TGUI vore panel but the game refuses to do so, for some reason. It only loads the vore spritesheet.
- name = "colorizedvore"
-
-/datum/asset/spritesheet/vore_colorized/create_spritesheets()
- var/icon/downscaledVC = icon('icons/mob/screen_full_colorized_vore.dmi')
- downscaledVC.Scale(240, 240)
- InsertAll("", downscaledVC)
-
-//VOREStation Add End
-
-// // Representative icons for each research design
-// /datum/asset/spritesheet/research_designs
-// name = "design"
-
-// /datum/asset/spritesheet/research_designs/register()
-// for (var/path in subtypesof(/datum/design))
-// var/datum/design/D = path
-
-// var/icon_file
-// var/icon_state
-// var/icon/I
-
-// if(initial(D.research_icon) && initial(D.research_icon_state)) //If the design has an icon replacement skip the rest
-// icon_file = initial(D.research_icon)
-// icon_state = initial(D.research_icon_state)
-// if(!(icon_state in icon_states(icon_file)))
-// warning("design [D] 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(D.build_path)
-// if (!ispath(item, /atom))
-// // biogenerator outputs to beakers by default
-// if (initial(D.build_type) & BIOGENERATOR)
-// item = /obj/item/reagent_containers/glass/beaker/large
-// else
-// continue // shouldn't happen, but just in case
-
-// // circuit boards become their resulting machines or computers
-// if (ispath(item, /obj/item/circuitboard))
-// var/obj/item/circuitboard/C = item
-// var/machine = initial(C.build_path)
-// if (machine)
-// item = machine
-
-// icon_file = initial(item.icon)
-// icon_state = initial(item.icon_state)
-
-// if(!(icon_state in icon_states(icon_file)))
-// warning("design [D] 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))
-// 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)
-// if (keyboard && (keyboard in all_states))
-// I.Blend(icon(icon_file, keyboard, SOUTH), ICON_OVERLAY)
-
-// Insert(initial(D.id), I)
-// return ..()
-
-/datum/asset/spritesheet/vending
- name = "vending"
-
-/datum/asset/spritesheet/vending/create_spritesheets()
- populate_vending_products()
- for(var/atom/item as anything in GLOB.vending_products)
- if(!ispath(item, /atom))
- continue
-
- var/icon_file = initial(item.icon)
- var/icon_state = initial(item.icon_state)
-
- // I really don't like the fact that I have to do this, but what the hell else *can* I do to make all of these
- // random special items work?
- if(ispath(item, /obj/item/reagent_containers/food/drinks/glass2) && !ispath(item, /obj/item/reagent_containers/food/drinks/glass2/fitnessflask))
- var/obj/item/reagent_containers/food/drinks/glass2/G = item
- icon_state = initial(G.base_icon)
- if(ispath(item, /obj/item/reagent_containers/hypospray/autoinjector))
- icon_state += "0"
-
- var/icon/I
-
- var/icon_states_list = icon_states(icon_file)
- if(icon_state in icon_states_list)
- I = icon(icon_file, icon_state, SOUTH)
- var/c = initial(item.color)
- if(!isnull(c) && c != "#FFFFFF")
- I.Blend(c, ICON_MULTIPLY)
- else
- var/icon_states_string
- for(var/an_icon_state in icon_states_list)
- if(!icon_states_string)
- icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])"
- else
- icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])"
- stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]")
- I = icon('icons/turf/floors.dmi', "", SOUTH)
-
- var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
-
- Insert(imgid, I)
-
-// this is cursed but necessary or else vending product icons can be missing
-// basically, if there's any vending machines that aren't already mapped in, our register() will not know
-// that they exist, and therefore can't generate the entries in the spritesheet for them
-// and since assets are unique and can't be reloaded later, we have to make sure that GLOB.vending_products
-// is populated with every single type of vending machine
-// As this is only done at runtime, we have to create all the vending machines in existence and force them
-// to register their products when this asset initializes.
-/datum/asset/spritesheet/vending/proc/populate_vending_products()
- SSatoms.map_loader_begin()
- for(var/path in subtypesof(/obj/machinery/vending))
- var/obj/machinery/vending/x = new path(null)
- // force an inventory build; with map_loader_begin active, init isn't called
- x.build_inventory()
- qdel(x)
- SSatoms.map_loader_stop()
-
-// /datum/asset/simple/genetics
-// assets = list(
-// "dna_discovered.gif" = 'html/dna_discovered.gif',
-// "dna_undiscovered.gif" = 'html/dna_undiscovered.gif',
-// "dna_extra.gif" = 'html/dna_extra.gif'
-// )
-
-// /datum/asset/simple/orbit
-// assets = list(
-// "ghost.png" = 'html/ghost.png'
-// )
-
-// /datum/asset/simple/vv
-// assets = list(
-// "view_variables.css" = 'html/admin/view_variables.css'
-// )
-
-// /datum/asset/spritesheet/sheetmaterials
-// name = "sheetmaterials"
-
-// /datum/asset/spritesheet/sheetmaterials/register()
-// InsertAll("", 'icons/obj/stack_objects.dmi')
-
-// // Special case to handle Bluespace Crystals
-// Insert("polycrystal", 'icons/obj/telescience.dmi', "polycrystal")
-// ..()
-
-//Pill sprites for UIs
-/datum/asset/spritesheet/chem_master
- name = "chem_master"
-
-/datum/asset/spritesheet/chem_master/create_spritesheets()
- for(var/i = 1 to 24)
- Insert("pill[i]", 'icons/obj/chemical.dmi', "pill[i]")
-
- for(var/i = 1 to 4)
- Insert("bottle-[i]", 'icons/obj/chemical.dmi', "bottle-[i]")
-
-
-// Robot UI sprites
-/datum/asset/spritesheet/robot_icons
- name = "robot_icons"
-
-/datum/asset/spritesheet/robot_icons/create_spritesheets()
- for(var/datum/robot_sprite/S as anything in typesof(/datum/robot_sprite))
- if(!S.name || !S.sprite_icon_state) // snowflake out those customs... they suck
- continue
- var/icon/I_N = icon(S.sprite_icon, S.sprite_icon_state, NORTH)
- var/icon/I_S = icon(S.sprite_icon, S.sprite_icon_state, SOUTH)
- var/icon/I_W = icon(S.sprite_icon, S.sprite_icon_state, WEST)
- var/icon/I_E = icon(S.sprite_icon, S.sprite_icon_state, EAST)
- if(S.has_eye_sprites)
- var/icon/I_NE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", NORTH)
- if(I_NE)
- I_N.Blend(I_NE, ICON_OVERLAY)
- if(S.has_eye_sprites)
- var/icon/I_SE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", SOUTH)
- if(I_SE)
- I_S.Blend(I_SE, ICON_OVERLAY)
- if(S.has_eye_sprites)
- var/icon/I_WE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", WEST)
- if(I_WE)
- I_W.Blend(I_WE, ICON_OVERLAY)
- if(S.has_eye_sprites)
- var/icon/I_EE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", EAST)
- if(I_EE)
- I_E.Blend(I_EE, ICON_OVERLAY)
- var/imgid = sanitize_css_class_name("[S.type]")
- I_N.Scale(120, 120)
- I_S.Scale(120, 120)
- I_W.Scale(120, 120)
- I_E.Scale(120, 120)
- Insert(imgid + "N", I_N)
- Insert(imgid + "S", I_S)
- Insert(imgid + "W", I_W)
- Insert(imgid + "E", I_E)
-
-
-//Cloning pod sprites for UIs
-/datum/asset/simple/cloning
- assets = list(
- "pod_idle.gif" = 'icons/UI_Icons/synthprinter.gif',
- "pod_cloning.gif" = 'icons/UI_Icons/synthprinter_working.gif',
- )
-
-// VOREStation Add
-/datum/asset/simple/cloning/resleeving
- assets = list(
- "sleeve_empty.gif" = 'icons/UI_Icons/sleeve_empty.gif',
- "sleeve_occupied.gif" = 'icons/UI_Icons/sleeve_occupied.gif',
- "synthprinter.gif" = 'icons/UI_Icons/synthprinter.gif',
- "synthprinter_working.gif" = 'icons/UI_Icons/synthprinter_working.gif',
- )
-
-/datum/asset/spritesheet/sheetmaterials
- name = "sheetmaterials"
-
-/datum/asset/spritesheet/sheetmaterials/create_spritesheets()
- InsertAll("", 'icons/obj/stacks.dmi')
-
-// Nanomaps
-/datum/asset/simple/nanomaps
- // It REALLY doesnt matter too much if these arent up to date
- // They are relatively big
- assets = list(
- // YW Edit: We don't need Southern Cross, groundbase or tether
- // "southern_cross_nanomap_z1.png" = 'icons/_nanomaps/southern_cross_nanomap_z1.png',
- // "southern_cross_nanomap_z10.png" = 'icons/_nanomaps/southern_cross_nanomap_z10.png',
- // "southern_cross_nanomap_z2.png" = 'icons/_nanomaps/southern_cross_nanomap_z2.png',
- // "southern_cross_nanomap_z3.png" = 'icons/_nanomaps/southern_cross_nanomap_z3.png',
- // "southern_cross_nanomap_z5.png" = 'icons/_nanomaps/southern_cross_nanomap_z5.png',
- // "southern_cross_nanomap_z6.png" = 'icons/_nanomaps/southern_cross_nanomap_z6.png',
- //"tether_nanomap_z1.png" = 'icons/_nanomaps/tether_nanomap_z1.png',
- //"tether_nanomap_z2.png" = 'icons/_nanomaps/tether_nanomap_z2.png',
- //"tether_nanomap_z3.png" = 'icons/_nanomaps/tether_nanomap_z3.png',
- //"tether_nanomap_z4.png" = 'icons/_nanomaps/tether_nanomap_z4.png',
- //"tether_nanomap_z5.png" = 'icons/_nanomaps/tether_nanomap_z5.png',
- //"tether_nanomap_z6.png" = 'icons/_nanomaps/tether_nanomap_z6.png',
- //"tether_nanomap_z7.png" = 'icons/_nanomaps/tether_nanomap_z7.png',
- //"tether_nanomap_z8.png" = 'icons/_nanomaps/tether_nanomap_z8.png',
- //"tether_nanomap_z9.png" = 'icons/_nanomaps/tether_nanomap_z9.png',
- //"tether_nanomap_z10.png" = 'icons/_nanomaps/tether_nanomap_z10.png',
- //"tether_nanomap_z13.png" = 'icons/_nanomaps/tether_nanomap_z13.png',
- //"tether_nanomap_z14.png" = 'icons/_nanomaps/tether_nanomap_z14.png',
- //"stellardelight_nanomap_z1.png" = 'icons/_nanomaps/sd_deck1.png',
- //"stellardelight_nanomap_z2.png" = 'icons/_nanomaps/sd_deck2.png',
- //"stellardelight_nanomap_z3.png" = 'icons/_nanomaps/sd_deck3.png',
- //"groundbase_nanomap_z1.png" = 'icons/_nanomaps/gb1.png',
- //"groundbase_nanomap_z2.png" = 'icons/_nanomaps/gb2.png',
- //"groundbase_nanomap_z3.png" = 'icons/_nanomaps/gb3.png',
- //"groundbase_nanomap_z4.png" = 'icons/_nanomaps/gbnorth.png',
- //"groundbase_nanomap_z5.png" = 'icons/_nanomaps/gbsouth.png',
- //"groundbase_nanomap_z6.png" = 'icons/_nanomaps/gbeast.png',
- //"groundbase_nanomap_z7.png" = 'icons/_nanomaps/gbwest.png',
- //"groundbase_nanomap_z10.png" = 'icons/_nanomaps/gbmining.png',
- "cryogaia_nanomap_z2.png" = 'icons/_nanomaps/cryogaia_nanomap_z2.png',
- "cryogaia_nanomap_z3.png" = 'icons/_nanomaps/cryogaia_nanomap_z3.png',
- "cryogaia_nanomap_z4.png" = 'icons/_nanomaps/cryogaia_nanomap_z4.png',
- "cryogaia_nanomap_z5.png" = 'icons/_nanomaps/cryogaia_nanomap_z5.png',
- "cryogaia_nanomap_z6.png" = 'icons/_nanomaps/cryogaia_nanomap_z6.png',
- "cryogaia_nanomap_z13.png" = 'icons/_nanomaps/cryogaia_nanomap_z13.png',
- // YW Edit End
-
- )
diff --git a/code/modules/asset_cache/assets/browser.dm b/code/modules/asset_cache/assets/browser.dm
new file mode 100644
index 0000000000..8d5b4da198
--- /dev/null
+++ b/code/modules/asset_cache/assets/browser.dm
@@ -0,0 +1,43 @@
+/datum/asset/simple/generic
+ assets = list(
+ "search.js" = 'html/search.js',
+ "panels.css" = 'html/panels.css',
+ "loading.gif" = 'html/images/loading.gif',
+ "ntlogo.png" = 'html/images/ntlogo.png',
+ "sglogo.png" = 'html/images/sglogo.png',
+ "talisman.png" = 'html/images/talisman.png',
+ "paper_bg.png" = 'html/images/paper_bg.png',
+ "no_image32.png" = 'html/images/no_image32.png',
+ )
+
+/datum/asset/simple/changelog
+ assets = list(
+ "88x31.png" = 'html/88x31.png',
+ "bug-minus.png" = 'html/bug-minus.png',
+ "cross-circle.png" = 'html/cross-circle.png',
+ "hard-hat-exclamation.png" = 'html/hard-hat-exclamation.png',
+ "image-minus.png" = 'html/image-minus.png',
+ "image-plus.png" = 'html/image-plus.png',
+ "map-pencil.png" = 'html/map-pencil.png',
+ "music-minus.png" = 'html/music-minus.png',
+ "music-plus.png" = 'html/music-plus.png',
+ "tick-circle.png" = 'html/tick-circle.png',
+ "wrench-screwdriver.png" = 'html/wrench-screwdriver.png',
+ "spell-check.png" = 'html/spell-check.png',
+ "burn-exclamation.png" = 'html/burn-exclamation.png',
+ "chevron.png" = 'html/chevron.png',
+ "chevron-expand.png" = 'html/chevron-expand.png',
+ "changelog.css" = 'html/changelog.css',
+ "changelog.js" = 'html/changelog.js',
+ "changelog.html" = 'html/changelog.html'
+ )
+
+/datum/asset/simple/arcade
+ assets = list(
+ "boss1.gif" = 'icons/UI_Icons/Arcade/boss1.gif',
+ "boss2.gif" = 'icons/UI_Icons/Arcade/boss2.gif',
+ "boss3.gif" = 'icons/UI_Icons/Arcade/boss3.gif',
+ "boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif',
+ "boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif',
+ "boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif',
+ )
diff --git a/code/modules/asset_cache/assets/cloning.dm b/code/modules/asset_cache/assets/cloning.dm
new file mode 100644
index 0000000000..152a5041c5
--- /dev/null
+++ b/code/modules/asset_cache/assets/cloning.dm
@@ -0,0 +1,14 @@
+//Cloning pod sprites for UIs
+/datum/asset/simple/cloning
+ assets = list(
+ "pod_idle.gif" = 'icons/UI_Icons/synthprinter.gif',
+ "pod_cloning.gif" = 'icons/UI_Icons/synthprinter_working.gif',
+ )
+
+/datum/asset/simple/cloning/resleeving
+ assets = list(
+ "sleeve_empty.gif" = 'icons/UI_Icons/sleeve_empty.gif',
+ "sleeve_occupied.gif" = 'icons/UI_Icons/sleeve_occupied.gif',
+ "synthprinter.gif" = 'icons/UI_Icons/synthprinter.gif',
+ "synthprinter_working.gif" = 'icons/UI_Icons/synthprinter_working.gif',
+ )
diff --git a/code/modules/asset_cache/assets/kitchen_recipes.dm b/code/modules/asset_cache/assets/kitchen_recipes.dm
deleted file mode 100644
index 2cca2ed670..0000000000
--- a/code/modules/asset_cache/assets/kitchen_recipes.dm
+++ /dev/null
@@ -1,26 +0,0 @@
-/datum/asset/spritesheet/kitchen_recipes
- name = "kitchen_recipes"
-
-/datum/asset/spritesheet/kitchen_recipes/create_spritesheets()
- for(var/datum/recipe/R as anything in subtypesof(/datum/recipe))
- add_atom_icon(R.result, sanitize_css_class_name("[R.type]"))
-
-/datum/asset/spritesheet/kitchen_recipes/proc/add_atom_icon(typepath, id)
- var/icon_file
- var/icon_state
- var/obj/preview_item = typepath
-
- // if(ispath(ingredient_typepath, /datum/reagent))
- // var/datum/reagent/reagent = ingredient_typepath
- // preview_item = initial(reagent.default_container)
- // var/datum/glass_style/style = GLOB.glass_style_singletons[preview_item]?[reagent]
- // if(istype(style))
- // icon_file = style.icon
- // icon_state = style.icon_state
-
- // icon_file ||= initial(preview_item.icon_preview) || initial(preview_item.icon)
- // icon_state ||= initial(preview_item.icon_state_preview) || initial(preview_item.icon_state)
- icon_file = initial(preview_item.icon)
- icon_state = initial(preview_item.icon_state)
-
- Insert("[id]", icon_file, icon_state)
diff --git a/code/modules/asset_cache/assets/ntos.dm b/code/modules/asset_cache/assets/ntos.dm
new file mode 100644
index 0000000000..92fbc163d6
--- /dev/null
+++ b/code/modules/asset_cache/assets/ntos.dm
@@ -0,0 +1,30 @@
+/datum/asset/simple/headers
+ assets = list(
+ "alarm_green.gif" = 'icons/program_icons/alarm_green.gif',
+ "alarm_red.gif" = 'icons/program_icons/alarm_red.gif',
+ "batt_5.gif" = 'icons/program_icons/batt_5.gif',
+ "batt_20.gif" = 'icons/program_icons/batt_20.gif',
+ "batt_40.gif" = 'icons/program_icons/batt_40.gif',
+ "batt_60.gif" = 'icons/program_icons/batt_60.gif',
+ "batt_80.gif" = 'icons/program_icons/batt_80.gif',
+ "batt_100.gif" = 'icons/program_icons/batt_100.gif',
+ "charging.gif" = 'icons/program_icons/charging.gif',
+ "downloader_finished.gif" = 'icons/program_icons/downloader_finished.gif',
+ "downloader_running.gif" = 'icons/program_icons/downloader_running.gif',
+ "ntnrc_idle.gif" = 'icons/program_icons/ntnrc_idle.gif',
+ "ntnrc_new.gif" = 'icons/program_icons/ntnrc_new.gif',
+ "power_norm.gif" = 'icons/program_icons/power_norm.gif',
+ "power_warn.gif" = 'icons/program_icons/power_warn.gif',
+ "sig_high.gif" = 'icons/program_icons/sig_high.gif',
+ "sig_low.gif" = 'icons/program_icons/sig_low.gif',
+ "sig_lan.gif" = 'icons/program_icons/sig_lan.gif',
+ "sig_none.gif" = 'icons/program_icons/sig_none.gif',
+ "smmon_0.gif" = 'icons/program_icons/smmon_0.gif',
+ "smmon_1.gif" = 'icons/program_icons/smmon_1.gif',
+ "smmon_2.gif" = 'icons/program_icons/smmon_2.gif',
+ "smmon_3.gif" = 'icons/program_icons/smmon_3.gif',
+ "smmon_4.gif" = 'icons/program_icons/smmon_4.gif',
+ "smmon_5.gif" = 'icons/program_icons/smmon_5.gif',
+ "smmon_6.gif" = 'icons/program_icons/smmon_6.gif',
+ // "borg_mon.gif" = 'icons/program_icons/borg_mon.gif'
+ )
diff --git a/code/modules/asset_cache/assets/chat.dm b/code/modules/asset_cache/assets/spritesheets/chat.dm
similarity index 100%
rename from code/modules/asset_cache/assets/chat.dm
rename to code/modules/asset_cache/assets/spritesheets/chat.dm
diff --git a/code/modules/asset_cache/assets/spritesheets/chem_master.dm b/code/modules/asset_cache/assets/spritesheets/chem_master.dm
new file mode 100644
index 0000000000..f066d4b17e
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/chem_master.dm
@@ -0,0 +1,10 @@
+//Pill sprites for UIs
+/datum/asset/spritesheet/chem_master
+ name = "chem_master"
+
+/datum/asset/spritesheet/chem_master/create_spritesheets()
+ for(var/i = 1 to 24)
+ Insert("pill[i]", 'icons/obj/chemical.dmi', "pill[i]")
+
+ for(var/i = 1 to 4)
+ Insert("bottle-[i]", 'icons/obj/chemical.dmi', "bottle-[i]")
diff --git a/code/modules/asset_cache/assets/spritesheets/kitchen_recipes.dm b/code/modules/asset_cache/assets/spritesheets/kitchen_recipes.dm
new file mode 100644
index 0000000000..4fe048a84e
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/kitchen_recipes.dm
@@ -0,0 +1,13 @@
+/datum/asset/spritesheet_batched/kitchen_recipes
+ name = "kitchen_recipes"
+
+/datum/asset/spritesheet_batched/kitchen_recipes/create_spritesheets()
+ for(var/datum/recipe/R as anything in subtypesof(/datum/recipe))
+ var/key = sanitize_css_class_name("[R.type]")
+
+ var/atom/item = R.result
+ var/datum/universal_icon/icon = get_display_icon_for(item)
+ if(!icon || !icon.icon_file)
+ continue
+
+ insert_icon(key, icon)
diff --git a/code/modules/asset_cache/assets/spritesheets/nanomaps.dm b/code/modules/asset_cache/assets/spritesheets/nanomaps.dm
new file mode 100644
index 0000000000..049d19a536
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/nanomaps.dm
@@ -0,0 +1,38 @@
+/datum/asset/simple/nanomaps
+ // It REALLY doesnt matter too much if these arent up to date
+ // They are relatively big
+ assets = list(
+ // VOREStation Edit: We don't need Southern Cross
+ // "southern_cross_nanomap_z1.png" = 'icons/_nanomaps/southern_cross_nanomap_z1.png',
+ // "southern_cross_nanomap_z10.png" = 'icons/_nanomaps/southern_cross_nanomap_z10.png',
+ // "southern_cross_nanomap_z2.png" = 'icons/_nanomaps/southern_cross_nanomap_z2.png',
+ // "southern_cross_nanomap_z3.png" = 'icons/_nanomaps/southern_cross_nanomap_z3.png',
+ // "southern_cross_nanomap_z5.png" = 'icons/_nanomaps/southern_cross_nanomap_z5.png',
+ // "southern_cross_nanomap_z6.png" = 'icons/_nanomaps/southern_cross_nanomap_z6.png',
+ "tether_nanomap_z1.png" = 'icons/_nanomaps/tether_nanomap_z1.png',
+ "tether_nanomap_z2.png" = 'icons/_nanomaps/tether_nanomap_z2.png',
+ "tether_nanomap_z3.png" = 'icons/_nanomaps/tether_nanomap_z3.png',
+ "tether_nanomap_z4.png" = 'icons/_nanomaps/tether_nanomap_z4.png',
+ "tether_nanomap_z5.png" = 'icons/_nanomaps/tether_nanomap_z5.png',
+ "tether_nanomap_z6.png" = 'icons/_nanomaps/tether_nanomap_z6.png',
+ "tether_nanomap_z7.png" = 'icons/_nanomaps/tether_nanomap_z7.png',
+ "tether_nanomap_z8.png" = 'icons/_nanomaps/tether_nanomap_z8.png',
+ "tether_nanomap_z9.png" = 'icons/_nanomaps/tether_nanomap_z9.png',
+ "tether_nanomap_z10.png" = 'icons/_nanomaps/tether_nanomap_z10.png',
+ "tether_nanomap_z13.png" = 'icons/_nanomaps/tether_nanomap_z13.png',
+ "tether_nanomap_z14.png" = 'icons/_nanomaps/tether_nanomap_z14.png',
+ "stellardelight_nanomap_z1.png" = 'icons/_nanomaps/sd_deck1.png',
+ "stellardelight_nanomap_z2.png" = 'icons/_nanomaps/sd_deck2.png',
+ "stellardelight_nanomap_z3.png" = 'icons/_nanomaps/sd_deck3.png',
+ "groundbase_nanomap_z1.png" = 'icons/_nanomaps/gb1.png',
+ "groundbase_nanomap_z2.png" = 'icons/_nanomaps/gb2.png',
+ "groundbase_nanomap_z3.png" = 'icons/_nanomaps/gb3.png',
+ "groundbase_nanomap_z4.png" = 'icons/_nanomaps/gbnorth.png',
+ "groundbase_nanomap_z5.png" = 'icons/_nanomaps/gbsouth.png',
+ "groundbase_nanomap_z6.png" = 'icons/_nanomaps/gbeast.png',
+ "groundbase_nanomap_z7.png" = 'icons/_nanomaps/gbwest.png',
+ "groundbase_nanomap_z10.png" = 'icons/_nanomaps/gbmining.png',
+
+
+ // VOREStation Edit End
+ )
diff --git a/code/modules/asset_cache/assets/spritesheets/pipes.dm b/code/modules/asset_cache/assets/spritesheets/pipes.dm
new file mode 100644
index 0000000000..b0f5935a95
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/pipes.dm
@@ -0,0 +1,6 @@
+/datum/asset/spritesheet/pipes
+ name = "pipes"
+
+/datum/asset/spritesheet/pipes/create_spritesheets()
+ for(var/each in list('icons/obj/pipe-item.dmi', 'icons/obj/pipes/disposal.dmi'))
+ InsertAll("", each, global.alldirs)
diff --git a/code/modules/asset_cache/assets/spritesheets/robot_icons.dm b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
new file mode 100644
index 0000000000..7935c5af7e
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
@@ -0,0 +1,36 @@
+/datum/asset/spritesheet/robot_icons
+ name = "robot_icons"
+
+/datum/asset/spritesheet/robot_icons/create_spritesheets()
+ for(var/datum/robot_sprite/S as anything in typesof(/datum/robot_sprite))
+ if(!S.name || !S.sprite_icon_state) // snowflake out those customs... they suck
+ continue
+ var/icon/I_N = icon(S.sprite_icon, S.sprite_icon_state, NORTH)
+ var/icon/I_S = icon(S.sprite_icon, S.sprite_icon_state, SOUTH)
+ var/icon/I_W = icon(S.sprite_icon, S.sprite_icon_state, WEST)
+ var/icon/I_E = icon(S.sprite_icon, S.sprite_icon_state, EAST)
+ if(S.has_eye_sprites)
+ var/icon/I_NE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", NORTH)
+ if(I_NE)
+ I_N.Blend(I_NE, ICON_OVERLAY)
+ if(S.has_eye_sprites)
+ var/icon/I_SE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", SOUTH)
+ if(I_SE)
+ I_S.Blend(I_SE, ICON_OVERLAY)
+ if(S.has_eye_sprites)
+ var/icon/I_WE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", WEST)
+ if(I_WE)
+ I_W.Blend(I_WE, ICON_OVERLAY)
+ if(S.has_eye_sprites)
+ var/icon/I_EE = icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", EAST)
+ if(I_EE)
+ I_E.Blend(I_EE, ICON_OVERLAY)
+ var/imgid = sanitize_css_class_name("[S.type]")
+ I_N.Scale(120, 120)
+ I_S.Scale(120, 120)
+ I_W.Scale(120, 120)
+ I_E.Scale(120, 120)
+ Insert(imgid + "N", I_N)
+ Insert(imgid + "S", I_S)
+ Insert(imgid + "W", I_W)
+ Insert(imgid + "E", I_E)
diff --git a/code/modules/asset_cache/assets/spritesheets/stacks.dm b/code/modules/asset_cache/assets/spritesheets/stacks.dm
new file mode 100644
index 0000000000..2dd3093663
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/stacks.dm
@@ -0,0 +1,5 @@
+/datum/asset/spritesheet/sheetmaterials
+ name = "sheetmaterials"
+
+/datum/asset/spritesheet/sheetmaterials/create_spritesheets()
+ InsertAll("", 'icons/obj/stacks.dmi')
diff --git a/code/modules/asset_cache/assets/spritesheets/vending.dm b/code/modules/asset_cache/assets/spritesheets/vending.dm
new file mode 100644
index 0000000000..c1ceafd814
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/vending.dm
@@ -0,0 +1,57 @@
+/datum/asset/spritesheet/vending
+ name = "vending"
+
+/datum/asset/spritesheet/vending/create_spritesheets()
+ populate_vending_products()
+ for(var/atom/item as anything in GLOB.vending_products)
+ if(!ispath(item, /atom))
+ continue
+
+ var/icon_file = initial(item.icon)
+ var/icon_state = initial(item.icon_state)
+
+ // I really don't like the fact that I have to do this, but what the hell else *can* I do to make all of these
+ // random special items work?
+ if(ispath(item, /obj/item/reagent_containers/food/drinks/glass2) && !ispath(item, /obj/item/reagent_containers/food/drinks/glass2/fitnessflask))
+ var/obj/item/reagent_containers/food/drinks/glass2/G = item
+ icon_state = initial(G.base_icon)
+ if(ispath(item, /obj/item/reagent_containers/hypospray/autoinjector))
+ icon_state += "0"
+
+ var/icon/I
+
+ var/icon_states_list = icon_states(icon_file)
+ if(icon_state in icon_states_list)
+ I = icon(icon_file, icon_state, SOUTH)
+ var/c = initial(item.color)
+ if(!isnull(c) && c != "#FFFFFF")
+ I.Blend(c, ICON_MULTIPLY)
+ else
+ var/icon_states_string
+ for(var/an_icon_state in icon_states_list)
+ if(!icon_states_string)
+ icon_states_string = "[json_encode(an_icon_state)](\ref[an_icon_state])"
+ else
+ icon_states_string += ", [json_encode(an_icon_state)](\ref[an_icon_state])"
+ stack_trace("[item] does not have a valid icon state, icon=[icon_file], icon_state=[json_encode(icon_state)](\ref[icon_state]), icon_states=[icon_states_string]")
+ I = icon('icons/turf/floors.dmi', "", SOUTH)
+
+ var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-")
+
+ Insert(imgid, I)
+
+// this is cursed but necessary or else vending product icons can be missing
+// basically, if there's any vending machines that aren't already mapped in, our register() will not know
+// that they exist, and therefore can't generate the entries in the spritesheet for them
+// and since assets are unique and can't be reloaded later, we have to make sure that GLOB.vending_products
+// is populated with every single type of vending machine
+// As this is only done at runtime, we have to create all the vending machines in existence and force them
+// to register their products when this asset initializes.
+/datum/asset/spritesheet/vending/proc/populate_vending_products()
+ SSatoms.map_loader_begin()
+ for(var/path in subtypesof(/obj/machinery/vending))
+ var/obj/machinery/vending/x = new path(null)
+ // force an inventory build; with map_loader_begin active, init isn't called
+ x.build_inventory()
+ qdel(x)
+ SSatoms.map_loader_stop()
diff --git a/code/modules/asset_cache/assets/spritesheets/vore.dm b/code/modules/asset_cache/assets/spritesheets/vore.dm
new file mode 100644
index 0000000000..625043d89d
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/vore.dm
@@ -0,0 +1,15 @@
+/datum/asset/spritesheet/vore
+ name = "vore"
+
+/datum/asset/spritesheet/vore/create_spritesheets()
+ var/icon/downscaled = icon('icons/mob/screen_full_vore.dmi')
+ downscaled.Scale(240, 240)
+ InsertAll("", downscaled)
+
+/datum/asset/spritesheet/vore_colorized //This should be getting loaded in the TGUI vore panel but the game refuses to do so, for some reason. It only loads the vore spritesheet.
+ name = "colorizedvore"
+
+/datum/asset/spritesheet/vore_colorized/create_spritesheets()
+ var/icon/downscaledVC = icon('icons/mob/screen_full_colorized_vore.dmi')
+ downscaledVC.Scale(240, 240)
+ InsertAll("", downscaledVC)
diff --git a/code/modules/asset_cache/iconforge/batched_spritesheet.dm b/code/modules/asset_cache/iconforge/batched_spritesheet.dm
new file mode 100644
index 0000000000..c49248f417
--- /dev/null
+++ b/code/modules/asset_cache/iconforge/batched_spritesheet.dm
@@ -0,0 +1,356 @@
+#define SPR_SIZE "size_id"
+#define SPR_IDX "position"
+#define CACHE_WAIT "wait"
+#define CACHE_INVALID TRUE
+#define CACHE_VALID FALSE
+
+#define WHATTHEFUCKAMIDOING_FILE "data/WHATTHEFUCKFILE.json"
+
+/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
+ /// Avoid propogating 'Could not find associated icon state' errors because we know our input data fucking sucks
+ var/ignore_associated_icon_state_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
+ 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, "[log_path]-spritesheet_cache_debug.[name].json")
+ rustg_file_write(entries_json, "[log_path]-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.")
+ 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()
+ SHOULD_CALL_PARENT(FALSE)
+ 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))
+ if(icon_state_name == "")
+ icon_state_name = "byond-default"
+ 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
+ //rustg_file_write(entries_json, "fuckoff.json")
+ 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, "[log_path]-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/file_path = "data/spritesheets/[name]_[size_id].png"
+ var/file_hash = rustg_hash_file("md5", file_path)
+ SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(file_path), file_hash)
+ var/res_name = "spritesheet_[name].css"
+ var/fname = "data/spritesheets/[res_name]"
+
+ fdel(fname)
+ var/css = generate_css()
+ rustg_file_write(css, fname)
+ var/css_hash = rustg_hash_string("md5", css)
+ SSassets.transport.register_asset(res_name, fcopy_rsc(fname), file_hash=css_hash)
+
+ 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"])
+ var/err = data["error"]
+ if(ignore_dir_errors && findtext(err, "is not in the set of valid dirs"))
+ return
+ if(ignore_associated_icon_state_errors && findtext(err, "Could not find associated icon state"))
+ return
+ CRASH("Error during spritesheet generation for [name]: [err]")
+
+/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: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_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_fname = "data/spritesheets/spritesheet_[name].css"
+
+ // 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("md5", css_fname)
+ SSassets.transport.register_asset("spritesheet_[name].css", fcopy_rsc(css_fname), file_hash=css_hash)
+ for(var/size_id in sizes)
+ var/fname = "data/spritesheets/[name]_[size_id].png"
+ var/hash = rustg_hash_file("md5", fname)
+ SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(fname), file_hash=hash)
+
+ 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()
+ )
+ 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 WHATTHEFUCKAMIDOING_FILE
diff --git a/code/modules/asset_cache/iconforge/universal_icon.dm b/code/modules/asset_cache/iconforge/universal_icon.dm
new file mode 100644
index 0000000000..3446151c9b
--- /dev/null
+++ b/code/modules/asset_cache/iconforge/universal_icon.dm
@@ -0,0 +1,186 @@
+/// 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")
+ // 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
+
+/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(target["color"], target["blend_mode"])
+ if(RUSTG_ICONFORGE_BLEND_ICON)
+ var/datum/universal_icon/icon_object = target["icon"]
+ if(!istype(icon_object))
+ stack_trace("Invalid icon found in icon transformer during apply()! [icon_object]")
+ continue
+ target.Blend(icon_object.to_icon(), target["blend_mode"])
+ if(RUSTG_ICONFORGE_SCALE)
+ target.Scale(target["width"], target["height"])
+ if(RUSTG_ICONFORGE_CROP)
+ target.Crop(target["x1"], target["y1"], target["x2"], target["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)
+ 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)
+ transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode))
+
+/datum/icon_transformer/proc/scale(width, height)
+ transforms += list(list("type" = RUSTG_ICONFORGE_SCALE, "width" = width, "height" = height))
+
+/datum/icon_transformer/proc/crop(x1, y1, x2, y2)
+ transforms += list(list("type" = RUSTG_ICONFORGE_CROP, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2))
+
+/datum/icon_transformer/proc/to_list()
+ RETURN_TYPE(/list)
+ var/list/transforms_out = list()
+ for(var/transform in src.transforms.Copy())
+ var/this_transform = transform
+ 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_transform["icon"] = icon_object.to_list()
+ transforms_out += list(this_transform)
+ return transforms_out
+
+/proc/icon_transformer_from_list(list/input)
+ RETURN_TYPE(/datum/icon_transformer)
+ var/list/transforms = list()
+ for(var/transform in input)
+ var/this_transform = transform
+ 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 item to a universal icon by generating blend operations.
+// /proc/gags_to_universal_icon(obj/item/path)
+// RETURN_TYPE(/datum/universal_icon)
+// if(!ispath(path, /obj/item) || !initial(path.greyscale_config) || !initial(path.greyscale_colors))
+// CRASH("gags_to_universal_icon() received an invalid path!")
+// var/datum/greyscale_config/config = initial(path.greyscale_config)
+// var/colors = initial(path.greyscale_colors)
+// var/datum/universal_icon/entry = SSgreyscale.GetColoredIconEntryByType(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/A)
+ if (!ispath(A, /atom))
+ return FALSE
+ var/icon_file = initial(A.icon)
+ var/icon_state = initial(A.icon_state)
+ // if(ispath(A, /obj/item))
+ // var/obj/item/I = A
+ // if(initial(I.icon_state_preview))
+ // icon_state = initial(I.icon_state_preview)
+ // if(initial(I.greyscale_config) && initial(I.greyscale_colors))
+ // return gags_to_universal_icon(I)
+ return uni_icon(icon_file, icon_state, color=initial(A.color))
diff --git a/code/modules/asset_cache/transports/asset_transport.dm b/code/modules/asset_cache/transports/asset_transport.dm
index db19277abf..77daee2a60 100644
--- a/code/modules/asset_cache/transports/asset_transport.dm
+++ b/code/modules/asset_cache/transports/asset_transport.dm
@@ -1,6 +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
var/name = "Simple browse_rsc asset transport"
@@ -59,6 +56,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.
@@ -66,6 +67,8 @@
/datum/asset_transport/proc/get_asset_url(asset_name, datum/asset_cache_item/asset_cache_item)
if (!istype(asset_cache_item))
asset_cache_item = SSassets.cache[asset_name]
+ if (!istype(asset_cache_item))
+ return null
// To ensure code that breaks on cdns breaks in local testing, we only
// use the normal filename on legacy assets and name space assets.
var/keep_local_name = dont_mutate_filenames \
@@ -82,6 +85,9 @@
/// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum.
/// Returns TRUE if any assets were sent.
/datum/asset_transport/proc/send_assets(client/client, list/asset_list)
+#if defined(UNIT_TESTS)
+ return
+#endif
if (!istype(client))
if (ismob(client))
var/mob/M = client
@@ -143,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 e3cb33b8fa..d67d097b0b 100644
--- a/code/modules/asset_cache/transports/webroot_transport.dm
+++ b/code/modules/asset_cache/transports/webroot_transport.dm
@@ -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_file_path)
. = ..()
var/datum/asset_cache_item/ACI = .
diff --git a/code/modules/clothing/suits/hooded.dm b/code/modules/clothing/suits/hooded.dm
index aa7d1bf8fb..9efe8ab494 100644
--- a/code/modules/clothing/suits/hooded.dm
+++ b/code/modules/clothing/suits/hooded.dm
@@ -44,6 +44,7 @@
/obj/item/clothing/suit/storage/hooded/dropped()
RemoveHood()
+ ..()
/obj/item/clothing/suit/storage/hooded/proc/ToggleHood()
if(!hood_up)
diff --git a/code/modules/food/kitchen/microwave.dm b/code/modules/food/kitchen/microwave.dm
index 9cd7ccd1bc..9ebc0c15a5 100644
--- a/code/modules/food/kitchen/microwave.dm
+++ b/code/modules/food/kitchen/microwave.dm
@@ -253,7 +253,7 @@
/obj/machinery/microwave/ui_assets(mob/user)
return list(
- get_asset_datum(/datum/asset/spritesheet/kitchen_recipes)
+ get_asset_datum(/datum/asset/spritesheet_batched/kitchen_recipes)
)
/obj/machinery/microwave/tgui_static_data(mob/user)
diff --git a/code/modules/mob/_modifiers/feysight.dm b/code/modules/mob/_modifiers/feysight.dm
index 5ff99307b9..0cf07c935f 100644
--- a/code/modules/mob/_modifiers/feysight.dm
+++ b/code/modules/mob/_modifiers/feysight.dm
@@ -13,10 +13,12 @@
/datum/modifier/feysight/on_applied()
holder.see_invisible = 60
holder.see_invisible_default = 60
+ holder.plane_holder.set_vis(VIS_GHOSTS,TRUE)
/datum/modifier/feysight/on_expire()
holder.see_invisible_default = initial(holder.see_invisible_default)
holder.see_invisible = holder.see_invisible_default
+ holder.plane_holder.set_vis(VIS_GHOSTS,FALSE)
/datum/modifier/feysight/can_apply(var/mob/living/L)
if(L.stat)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 556c04083c..c278fcefa5 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -36,6 +36,7 @@
incorporeal_move = 1
var/is_manifest = 0 //If set to 1, the ghost is able to whisper. Usually only set if a cultist drags them through the veil.
+ var/toggled_invisible = 0
var/ghost_sprite = null
var/global/list/possible_ghost_sprites = list(
"Clear" = "blank",
@@ -841,15 +842,19 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/observer/dead/proc/manifest(mob/user)
is_manifest = TRUE
- add_verb(src, /mob/observer/dead/proc/toggle_visibility)
- add_verb(src, /mob/observer/dead/proc/ghost_whisper)
+ // Allows them to use the 'toggle_visibility' verb add_verb(src, /mob/observer/dead/verb/toggle_visibility)
+ // Allows them to use the 'ghost whisper' verb add_verb(src, /mob/observer/dead/verb/ghost_whisper)
to_chat(src, span_filter_notice(span_purple("As you are now in the realm of the living, you can whisper to the living with the " + span_bold("Spectral Whisper") + " verb, inside the IC tab.")))
+ if(!user)
+ visible_message(span_deadsay("The ghost of \the [src] is dragged back in to our plane of reality!"))
+ toggle_ghost_visibility(TRUE)
+ return
if(plane != PLANE_WORLD)
user.visible_message( \
span_warning("\The [user] drags ghost, [src], to our plane of reality!"), \
span_warning("You drag [src] to our plane of reality!") \
)
- toggle_visibility(TRUE)
+ toggle_ghost_visibility(TRUE)
else
var/datum/gender/T = gender_datums[user.get_visible_gender()]
user.visible_message ( \
@@ -871,12 +876,17 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
var/image/J = image('icons/mob/mob.dmi', loc = src, icon_state = icon)
client.images += J
-/mob/observer/dead/proc/toggle_visibility(var/forced = 0)
- set category = "Ghost.Settings"
+/mob/observer/dead/verb/toggle_visibility()
+
set name = "Toggle Visibility"
set desc = "Allows you to turn (in)visible (almost) at will."
+ set category = "Ghost.Settings"
+ toggle_ghost_visibility()
- var/toggled_invisible
+/mob/observer/dead/proc/toggle_ghost_visibility(var/forced = FALSE)
+ if(!is_manifest)
+ to_chat(src, span_filter_notice("You are not strong enough to pierce the veil..."))
+ return
if(!forced && plane == PLANE_GHOSTS && world.time < toggled_invisible + 600)
to_chat(src, span_filter_notice("You must gather strength before you can turn visible again..."))
return
@@ -972,7 +982,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
//Culted Ghosts
-/mob/observer/dead/proc/ghost_whisper()
+/mob/observer/dead/verb/ghost_whisper()
set name = "Spectral Whisper"
set category = "IC.Subtle"
@@ -992,7 +1002,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
return
return 1
else
- to_chat(src, span_danger("You have not been pulled past the veil!"))
+ to_chat(src, span_danger("You have not been pulled past the veil! You can not whisper to the living."))
/mob/observer/dead/verb/choose_ghost_sprite()
set category = "Ghost.Settings"
diff --git a/code/modules/mob/living/carbon/carbon_powers.dm b/code/modules/mob/living/carbon/carbon_powers.dm
index d6fdc94788..284224604d 100644
--- a/code/modules/mob/living/carbon/carbon_powers.dm
+++ b/code/modules/mob/living/carbon/carbon_powers.dm
@@ -60,3 +60,23 @@
else
to_chat(src, span_warning("You do not have enough chemicals stored to reproduce."))
return
+
+/mob/living/proc/toggle_active_cloaking() // Borrowed from Rogue Star, thanks guys!
+ set category = "Abilities.General"
+ set name = "Toggle Active Cloaking"
+
+ if(invisibility == INVISIBILITY_OBSERVER)
+ invisibility = initial(invisibility)
+ to_chat(src, span_notice("You are now visible."))
+ alpha = max(alpha + 100, 255)
+ else
+ invisibility = INVISIBILITY_OBSERVER
+ to_chat(src, span_notice("You are now invisible."))
+ alpha = max(alpha - 100, 0)
+
+ var/datum/effect/effect/system/spark_spread/sparks = new /datum/effect/effect/system/spark_spread()
+ sparks.set_up(5, 0, src)
+ sparks.attach(loc)
+ sparks.start()
+ visible_message(span_warning("Electrical sparks manifest around \the [src] as they suddenly appear!"))
+ qdel(sparks)
diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm
index 338c72451f..2cc6a561ea 100644
--- a/code/modules/mob/living/carbon/human/human_movement.dm
+++ b/code/modules/mob/living/carbon/human/human_movement.dm
@@ -302,3 +302,4 @@
. = ..()
if(. && (species.tail || tail_style))
update_tail_showing()
+ update_inv_wear_suit()
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index d23aa3d781..bd97cc2e0c 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -1631,7 +1631,11 @@
else //We aren't dead
sight &= ~(SEE_TURFS|SEE_MOBS|SEE_OBJS)
- see_invisible = see_in_dark>2 ? SEE_INVISIBLE_LEVEL_ONE : see_invisible_default
+
+ if(see_invisible_default > SEE_INVISIBLE_LEVEL_ONE)
+ see_invisible = see_invisible_default
+ else
+ see_invisible = see_in_dark>2 ? SEE_INVISIBLE_LEVEL_ONE : see_invisible_default
// Do this early so certain stuff gets turned off before vision is assigned.
var/area/A = get_area(src)
@@ -1659,7 +1663,10 @@
else
sight = species.get_vision_flags(src)
see_in_dark = species.darksight
- see_invisible = see_in_dark>2 ? SEE_INVISIBLE_LEVEL_ONE : see_invisible_default
+ if(see_invisible_default > SEE_INVISIBLE_LEVEL_ONE)
+ see_invisible = see_invisible_default
+ else
+ see_invisible = see_in_dark>2 ? SEE_INVISIBLE_LEVEL_ONE : see_invisible_default
var/glasses_processed = 0
var/obj/item/rig/rig = get_rig()
@@ -1673,7 +1680,8 @@
if(XRAY in mutations)
sight |= SEE_TURFS|SEE_MOBS|SEE_OBJS
see_in_dark = 8
- if(!druggy) see_invisible = SEE_INVISIBLE_LEVEL_TWO
+ if(!druggy)
+ see_invisible = SEE_INVISIBLE_LEVEL_TWO
for(var/datum/modifier/M in modifiers)
if(!isnull(M.vision_flags))
diff --git a/code/modules/mob/living/default_language.dm b/code/modules/mob/living/default_language.dm
index 66cdcb28ad..ff72f448f1 100644
--- a/code/modules/mob/living/default_language.dm
+++ b/code/modules/mob/living/default_language.dm
@@ -5,30 +5,34 @@
set name = "Set Default Language"
set category = "IC.Settings"
- var/language = tgui_input_list(usr, "Select your default language", "Available languages", languages)
+ if(!LAZYLEN(languages))
+ to_chat(src, span_warning("You can't speak any languages."))
+ return
+
+ var/language = tgui_input_list(src, "Select your default language", "Available languages", languages)
+ if(!language)
+ return
apply_default_language(language)
// Silicons can't neccessarily speak everything in their languages list
/mob/living/silicon/set_default_language()
- var/language = tgui_input_list(usr, "Select your default language", "Available languages", speech_synthesizer_langs)
- // Silicons have no species language usually. So let's default them to GALCOM
- if(!language)
- to_chat(src, span_notice("You will now speak your standard default language, common, if you do not specify a language when speaking."))
- for(var/datum/language/lang in speech_synthesizer_langs)
- if(lang.name == LANGUAGE_GALCOM)
- default_language = lang
- break
+ if(!LAZYLEN(speech_synthesizer_langs))
+ to_chat(src, span_warning("You can't speak any languages."))
return
+ var/language = tgui_input_list(src, "Select your default language", "Available languages", speech_synthesizer_langs)
+ if(!language)
+ return
+
apply_default_language(language)
/mob/living/proc/apply_default_language(var/language)
- if (only_species_language && language != GLOB.all_languages[src.species_language])
- to_chat(src, span_notice("You can only speak your species language, [src.species_language]."))
+ if (only_species_language && language != GLOB.all_languages[species_language])
+ to_chat(src, span_notice("You can only speak your species language, [species_language]."))
return 0
- if(language == GLOB.all_languages[src.species_language])
- to_chat(src, span_notice("You will now speak your standard default language, [language ? language : "common"], if you do not specify a language when speaking."))
+ if(language == GLOB.all_languages[species_language])
+ to_chat(src, span_notice("You will now speak your standard default language, [language], if you do not specify a language when speaking."))
else if (language)
if(language && !can_speak(language))
diff --git a/code/modules/mob/living/silicon/pai/pai_vr.dm b/code/modules/mob/living/silicon/pai/pai_vr.dm
index 49d34c454e..3db2418ce2 100644
--- a/code/modules/mob/living/silicon/pai/pai_vr.dm
+++ b/code/modules/mob/living/silicon/pai/pai_vr.dm
@@ -1,5 +1,4 @@
/mob/living/silicon/pai
- var/people_eaten = 0
icon = 'icons/mob/pai_vr.dmi'
softfall = TRUE
var/eye_glow = TRUE
@@ -156,14 +155,18 @@
add_eyes()
return
update_fullness()
- if(!people_eaten && !resting)
+ //Add a check when selecting a chassis if you add in support for this, to set vore_capacity to 2 or however many states you have.
+ var/fullness_extension = ""
+ if(vore_capacity > 1 && vore_fullness > 1)
+ fullness_extension = "_[vore_fullness]"
+ if(!vore_fullness && !resting)
icon_state = "[chassis]"
- else if(!people_eaten && resting)
+ else if(!vore_fullness && resting)
icon_state = "[chassis]_rest"
- else if(people_eaten && !resting)
- icon_state = "[chassis]_full"
- else if(people_eaten && resting)
- icon_state = "[chassis]_rest_full"
+ else if(vore_fullness && !resting)
+ icon_state = "[chassis]_full[fullness_extension]"
+ else if(vore_fullness && resting)
+ icon_state = "[chassis]_rest_full[fullness_extension]"
if(chassis in wide_chassis)
pixel_x = -16
default_pixel_x = -16
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 8f0b9a3412..032fe3ed43 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -24,6 +24,7 @@
var/crisis //Admin-settable for combat module use.
var/crisis_override = 0
var/integrated_light_power = 6
+ var/robotdecal_on = 0
var/datum/wires/robot/wires
can_be_antagged = TRUE
@@ -443,6 +444,13 @@
handle_light()
update_icon()
+/mob/living/silicon/robot/verb/toggle_robot_decals() // loads overlay UNDER lights.
+ set category = "Abilities.Silicon"
+ set name = "Toggle extras"
+ robotdecal_on = !robotdecal_on
+ to_chat(usr, span_filter_notice("You [robotdecal_on ? "enable" : "disable"] your extra apperances."))
+ update_icon()
+
/mob/living/silicon/robot/verb/spark_plug() //So you can still sparkle on demand without violence.
set category = "Abilities.Silicon"
set name = "Emit Sparks"
@@ -956,6 +964,12 @@
if(eyes_overlay)
add_overlay(eyes_overlay)
+ if(robotdecal_on && sprite_datum.has_robotdecal_sprites)
+ if(!shell || deployed) // Shell borgs that are not deployed will have no eyes.
+ var/robotdecal_overlay = sprite_datum.get_robotdecal_overlay(src)
+ if(robotdecal_overlay)
+ add_overlay(robotdecal_overlay)
+
if(lights_on && sprite_datum.has_eye_light_sprites)
if(!shell || deployed) // Shell borgs that are not deployed will have no eyes.
var/eyes_overlay = sprite_datum.get_eye_light_overlay(src)
diff --git a/code/modules/mob/living/silicon/robot/robot_ui_module.dm b/code/modules/mob/living/silicon/robot/robot_ui_module.dm
index ba69581df5..ef1545831c 100644
--- a/code/modules/mob/living/silicon/robot/robot_ui_module.dm
+++ b/code/modules/mob/living/silicon/robot/robot_ui_module.dm
@@ -42,7 +42,7 @@
modules.Add(shell_module_types)
else
modules.Add(robot_module_types)
- if(R.crisis || security_level == SEC_LEVEL_RED || R.crisis_override)
+ if(R.crisis || security_level >= SEC_LEVEL_RED || R.crisis_override)
to_chat(R, span_red("Crisis mode active. Combat module available."))
modules |= emergency_module_types
for(var/module_name in whitelisted_module_types)
diff --git a/code/modules/mob/living/silicon/robot/sprites/_sprite_datum.dm b/code/modules/mob/living/silicon/robot/sprites/_sprite_datum.dm
index 0d8135fe3e..8fe2277b0f 100644
--- a/code/modules/mob/living/silicon/robot/sprites/_sprite_datum.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/_sprite_datum.dm
@@ -10,6 +10,7 @@
var/has_eye_sprites = TRUE
var/has_eye_light_sprites = FALSE
+ var/has_robotdecal_sprites = FALSE
var/has_custom_open_sprites = FALSE
var/has_vore_belly_sprites = FALSE
var/has_vore_belly_resting_sprites = FALSE
@@ -131,6 +132,12 @@
else
return
+/datum/robot_sprite/proc/get_robotdecal_overlay(var/mob/living/silicon/robot/ourborg)
+ if(!(ourborg.resting && has_rest_sprites))
+ return "[sprite_icon_state]-decals"
+ else
+ return
+
/datum/robot_sprite/proc/get_rest_sprite(var/mob/living/silicon/robot/ourborg)
if(!(ourborg.rest_style in rest_sprite_options))
ourborg.rest_style = "Default"
@@ -181,21 +188,7 @@
has_dead_sprite_overlay = TRUE
has_custom_equipment_sprites = TRUE
pixel_x = -16
-/* //Does not need to be dogborg-only, letting all borgs use these -Reo
-/datum/robot_sprite/dogborg/get_rest_sprite(var/mob/living/silicon/robot/ourborg)
- if(!(ourborg.rest_style in rest_sprite_options))
- ourborg.rest_style = "Default"
- switch(ourborg.rest_style)
- if("Sit")
- return "[sprite_icon_state]-sit"
- if("Bellyup")
- return "[sprite_icon_state]-bellyup"
- else
- return "[sprite_icon_state]-rest"
-/datum/robot_sprite/dogborg/get_belly_overlay(var/mob/living/silicon/robot/ourborg)
- return "[sprite_icon_state]-sleeper"
-*/
/datum/robot_sprite/dogborg/do_equipment_glamour(var/obj/item/robot_module/module)
if(!has_custom_equipment_sprites)
return
diff --git a/code/modules/mob/living/silicon/robot/sprites/civilian.dm b/code/modules/mob/living/silicon/robot/sprites/civilian.dm
index d4b2664e63..05a0333d7d 100644
--- a/code/modules/mob/living/silicon/robot/sprites/civilian.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/civilian.dm
@@ -308,6 +308,27 @@
has_vore_belly_sprites = FALSE
rest_sprite_options = list("Default", "Bellyup")
+/datum/robot_sprite/dogborg/tall/service/dullahan
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/service/dullahan/basic
+ name = "Dullahan service v3"
+ sprite_icon_state = "dullahanservice"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
+
+/datum/robot_sprite/dogborg/tall/service/dullahanv3/barista
+ name = "Dullahan matcha v3"
+ sprite_icon_state = "dullahanbarista"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
// Clerical
diff --git a/code/modules/mob/living/silicon/robot/sprites/engineering.dm b/code/modules/mob/living/silicon/robot/sprites/engineering.dm
index 99ebeedb25..9ffaa658de 100644
--- a/code/modules/mob/living/silicon/robot/sprites/engineering.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/engineering.dm
@@ -212,4 +212,17 @@
has_eye_light_sprites = TRUE
has_custom_open_sprites = TRUE
has_vore_belly_sprites = FALSE
- rest_sprite_options = list("Default", "Bellyup")
\ No newline at end of file
+ rest_sprite_options = list("Default", "Bellyup")
+
+/datum/robot_sprite/dogborg/tall/engineering/dullahanv3
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/engineering/dullahanv3/engineerdully
+ name = "Dullahan engineer v3"
+ sprite_icon_state = "dullahanengineer"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
diff --git a/code/modules/mob/living/silicon/robot/sprites/janitor.dm b/code/modules/mob/living/silicon/robot/sprites/janitor.dm
index e634df2d29..cdc5f817ce 100644
--- a/code/modules/mob/living/silicon/robot/sprites/janitor.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/janitor.dm
@@ -191,10 +191,23 @@
has_vore_belly_sprites = FALSE
rest_sprite_options = list("Default", "Bellyup")
-/datum/robot_sprite/dogborg/tall/mining/k4t_alt1
+/datum/robot_sprite/dogborg/tall/janitor/k4t_alt1
name = "K4T Alt"
sprite_icon_state = "k4t_alt1"
has_eye_light_sprites = TRUE
has_custom_open_sprites = TRUE
has_vore_belly_sprites = FALSE
rest_sprite_options = list("Default", "Bellyup")
+
+/datum/robot_sprite/dogborg/tall/janitor/dullahanv3
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/janitor/dullahanv3/janitordully
+ name = "Dullahan janitor v3"
+ sprite_icon_state = "dullahanjanitor"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
diff --git a/code/modules/mob/living/silicon/robot/sprites/medical.dm b/code/modules/mob/living/silicon/robot/sprites/medical.dm
index 1a058aada4..f53b9894a2 100644
--- a/code/modules/mob/living/silicon/robot/sprites/medical.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/medical.dm
@@ -137,6 +137,18 @@
has_vore_belly_sprites = FALSE
rest_sprite_options = list("Default", "Bellyup")
+/datum/robot_sprite/dogborg/tall/medical/dullahanv3
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/medical/dullahanv3/medicaldully
+ name = "Dullahan medical v3"
+ sprite_icon_state = "dullahanmedical"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
// Surgeon
diff --git a/code/modules/mob/living/silicon/robot/sprites/mining.dm b/code/modules/mob/living/silicon/robot/sprites/mining.dm
index fba89f2c9e..d074a510a6 100644
--- a/code/modules/mob/living/silicon/robot/sprites/mining.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/mining.dm
@@ -182,4 +182,17 @@
has_eye_light_sprites = TRUE
has_custom_open_sprites = TRUE
has_vore_belly_sprites = FALSE
- rest_sprite_options = list("Default", "Bellyup")
\ No newline at end of file
+ rest_sprite_options = list("Default", "Bellyup")
+
+/datum/robot_sprite/dogborg/tall/mining/dullahanv3
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/mining/dullahanv3/miningdully
+ name = "Dullahan mining v3"
+ sprite_icon_state = "dullahanmining"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
diff --git a/code/modules/mob/living/silicon/robot/sprites/science.dm b/code/modules/mob/living/silicon/robot/sprites/science.dm
index ee6ffdc178..51159d9cb3 100644
--- a/code/modules/mob/living/silicon/robot/sprites/science.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/science.dm
@@ -205,3 +205,16 @@
has_custom_open_sprites = TRUE
has_vore_belly_sprites = FALSE
rest_sprite_options = list("Default", "Bellyup")
+
+/datum/robot_sprite/dogborg/tall/science/dullahanv3
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/science/dullahanv3/sciencedully
+ name = "Dullahan science v3"
+ sprite_icon_state = "dullahanscience"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
diff --git a/code/modules/mob/living/silicon/robot/sprites/security.dm b/code/modules/mob/living/silicon/robot/sprites/security.dm
index 3a8cd71e07..8ddd148260 100644
--- a/code/modules/mob/living/silicon/robot/sprites/security.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/security.dm
@@ -211,3 +211,16 @@
has_custom_open_sprites = TRUE
has_vore_belly_sprites = FALSE
rest_sprite_options = list("Default", "Bellyup")
+
+/datum/robot_sprite/dogborg/tall/security/dullahanv3
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/security/dullahanv3/securitydully
+ name = "Dullahan security v3"
+ sprite_icon_state = "dullahansecurity"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
diff --git a/code/modules/mob/living/silicon/robot/sprites/standard.dm b/code/modules/mob/living/silicon/robot/sprites/standard.dm
index 6678633003..0190d40a67 100644
--- a/code/modules/mob/living/silicon/robot/sprites/standard.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/standard.dm
@@ -126,4 +126,21 @@
sprite_icon = 'icons/mob/robot/standard_large.dmi'
// None yet
-*/
\ No newline at end of file
+*/
+
+/datum/robot_sprite/dogborg/tall/standard
+ module_type = "Standard"
+ //sprite_icon = 'icons/mob/robot/standard_large.dmi' NOT USED YET
+
+/datum/robot_sprite/dogborg/tall/standard/dullahanv3
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ //named v3 to avoid conflicts with downstream
+
+/datum/robot_sprite/dogborg/tall/standard/dullahanv3/standarddully
+ name = "Dullahan standard v3"
+ sprite_icon_state = "dullahanstandard"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
diff --git a/code/modules/mob/living/silicon/robot/sprites/syndicate.dm b/code/modules/mob/living/silicon/robot/sprites/syndicate.dm
index 86d1a0ade3..685d934225 100644
--- a/code/modules/mob/living/silicon/robot/sprites/syndicate.dm
+++ b/code/modules/mob/living/silicon/robot/sprites/syndicate.dm
@@ -69,6 +69,15 @@
has_vore_belly_sprites = FALSE
rest_sprite_options = list("Default", "Bellyup")
+/datum/robot_sprite/dogborg/tall/syndie/dullahanv3syndi
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ sprite_icon_state = "dullahansyndi"
+ name = "Dullahan syndie v3"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
// Protector
@@ -158,6 +167,16 @@
sprite_icon = 'icons/mob/robot/syndie_large.dmi'
sprite_hud_icon_state = "malf"
+/datum/robot_sprite/dogborg/tall/mechanist/dullahanv3mech
+ sprite_icon = 'icons/mob/tallborg/dullahans64x64.dmi'
+ sprite_icon_state = "dullahanmechanist"
+ name = "Dullahan mechanist v3"
+ has_eye_light_sprites = TRUE
+ has_custom_open_sprites = TRUE
+ has_vore_belly_sprites = TRUE
+ has_robotdecal_sprites = TRUE
+ rest_sprite_options = list("Default", "sit")
+
/datum/robot_sprite/dogborg/tall/mechanist/syndimechraptor
name = "Raptor V-4"
sprite_icon_state = "syndimechraptor"
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 3f917a15ce..9ee2d05f50 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -33,7 +33,7 @@
silicon_mob_list |= src
..()
add_language(LANGUAGE_GALCOM)
- set_default_language(GLOB.all_languages[LANGUAGE_GALCOM])
+ apply_default_language(GLOB.all_languages[LANGUAGE_GALCOM])
init_id()
init_subsystems()
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm b/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm
index ba0e5becbe..d64d84e250 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/pakkun.dm
@@ -81,11 +81,13 @@
return
if(resting)
- vore_selected.digest_mode = DM_UNABSORB
+ if(isbelly(vore_selected))
+ vore_selected.digest_mode = DM_UNABSORB
ai_holder.go_sleep()
else
- vore_selected.digest_mode = vore_default_mode
+ if(isbelly(vore_selected))
+ vore_selected.digest_mode = vore_default_mode
ai_holder.go_wake()
/mob/living/simple_mob/vore/pakkun/attack_hand(mob/user)
diff --git a/code/modules/mob/living/voice/voice.dm b/code/modules/mob/living/voice/voice.dm
index 3924029ece..9f8c640874 100644
--- a/code/modules/mob/living/voice/voice.dm
+++ b/code/modules/mob/living/voice/voice.dm
@@ -9,7 +9,7 @@
/mob/living/voice/Initialize(loc)
add_language(LANGUAGE_GALCOM)
- set_default_language(GLOB.all_languages[LANGUAGE_GALCOM])
+ apply_default_language(GLOB.all_languages[LANGUAGE_GALCOM])
if(istype(loc, /obj/item/communicator))
comm = loc
diff --git a/code/modules/organs/subtypes/machine.dm b/code/modules/organs/subtypes/machine.dm
index bbd7513240..7cdff12d42 100644
--- a/code/modules/organs/subtypes/machine.dm
+++ b/code/modules/organs/subtypes/machine.dm
@@ -46,12 +46,15 @@
stored_mmi = null
return ..()
-/obj/item/organ/internal/mmi_holder/New(var/mob/living/carbon/human/new_owner, var/internal)
+/obj/item/organ/internal/mmi_holder/New(var/mob/living/carbon/human/new_owner, var/internal, var/obj/item/mmi/installed)
..(new_owner, internal)
var/mob/living/carbon/human/dummy/mannequin/M = new_owner
if(istype(M))
return
- stored_mmi = new brain_type(src)
+ if(installed)
+ stored_mmi = installed
+ else
+ stored_mmi = new brain_type(src)
sleep(-1)
update_from_mmi()
diff --git a/code/modules/surgery/robotics.dm b/code/modules/surgery/robotics.dm
index ab5551855b..8c6795f67f 100644
--- a/code/modules/surgery/robotics.dm
+++ b/code/modules/surgery/robotics.dm
@@ -491,20 +491,18 @@
var/obj/item/mmi/M = tool
// VOREstation edit begin - Select the proper mmi holder subtype based on the brain inserted
var/obj/item/organ/internal/mmi_holder/holder = null
+ user.drop_from_inventory(M)
+ M.loc = holder
if(istype(M,/obj/item/mmi/digital/posibrain/nano))
- holder = new /obj/item/organ/internal/mmi_holder/posibrain/nano(target, 1)
+ holder = new /obj/item/organ/internal/mmi_holder/posibrain/nano(target, 1, M)
else if(istype(M,/obj/item/mmi/digital/posibrain))
- holder = new /obj/item/organ/internal/mmi_holder/posibrain(target, 1)
+ holder = new /obj/item/organ/internal/mmi_holder/posibrain(target, 1, M)
else if(istype(M,/obj/item/mmi/digital/robot))
- holder = new /obj/item/organ/internal/mmi_holder/robot(target, 1)
+ holder = new /obj/item/organ/internal/mmi_holder/robot(target, 1, M)
else
- holder = new /obj/item/organ/internal/mmi_holder(target, 1) // Fallback to old behavior if organic MMI or if no subtype exists.
- //VOREstation edit end
+ holder = new /obj/item/organ/internal/mmi_holder(target, 1, M) // Fallback to old behavior if organic MMI or if no subtype exists.
+ //VOREstation edit end
target.internal_organs_by_name["brain"] = holder
- user.drop_from_inventory(tool)
- tool.loc = holder
- holder.stored_mmi = tool
- holder.update_from_mmi()
if(M.brainmob && M.brainmob.mind)
M.brainmob.mind.transfer_to(target)
diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm
index 9e0a5491ab..3bae28e39c 100644
--- a/code/modules/tgui/tgui_window.dm
+++ b/code/modules/tgui/tgui_window.dm
@@ -322,7 +322,10 @@
if(istype(asset, /datum/asset/spritesheet))
var/datum/asset/spritesheet/spritesheet = asset
send_message("asset/stylesheet", spritesheet.css_filename())
- send_message("asset/mappings", asset.get_url_mappings())
+ 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())
/**
* private
diff --git a/code/modules/vore/eating/bellymodes_datum_vr.dm b/code/modules/vore/eating/bellymodes_datum_vr.dm
index 54c2ff9c10..3fd14c2ec6 100644
--- a/code/modules/vore/eating/bellymodes_datum_vr.dm
+++ b/code/modules/vore/eating/bellymodes_datum_vr.dm
@@ -142,10 +142,10 @@ GLOBAL_LIST_INIT(digest_modes, list())
if(L.size_multiplier > B.shrink_grow_size && B.owner.size_multiplier < 2) //Grow until either pred is large or prey is small.
B.owner.resize(B.owner.size_multiplier + 0.01) //Grow by 1% per tick.
if(B.owner.size_multiplier >= 2) // Adds some feedback so the pred knows they can't grow anymore.
- to_chat(B.owner, span_notice("You feel you have grown as much as you can."))
+ to_chat(B.owner, span_vnotice("You feel you have grown as much as you can."))
L.resize(L.size_multiplier - 0.01) //Shrink by 1% per tick
if(L.size_multiplier <= B.shrink_grow_size) // Adds some feedback so the pred knows their prey has stopped shrinking.
- to_chat(B.owner, span_notice("You feel [L] get as small as you would like within your [lowertext(B.name)]."))
+ to_chat(B.owner, span_vnotice("You feel [L] get as small as you would like within your [lowertext(B.name)]."))
B.owner.update_fullness()
. = ..()
diff --git a/config/example/resources.txt b/config/example/resources.txt
index f9a536e341..338cbcd408 100644
--- a/config/example/resources.txt
+++ b/config/example/resources.txt
@@ -31,6 +31,13 @@
## but enabled on production (the default).
#CACHE_ASSETS
+## 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.
+## 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/dependencies.sh b/dependencies.sh
index 57e14e9c8c..89b861043f 100644
--- a/dependencies.sh
+++ b/dependencies.sh
@@ -5,10 +5,13 @@
# byond version
export BYOND_MAJOR=515
-export BYOND_MINOR=1637
+export BYOND_MINOR=1647
+
+# Macro Count
+export MACRO_COUNT=8
#rust_g git tag
-export RUST_G_VERSION=3.3.0
+export RUST_G_VERSION=3.5.1
#node version
export NODE_VERSION_LTS=20.13.0
diff --git a/icons/mob/tallborg/dullahan_credit.txt b/icons/mob/tallborg/dullahan_credit.txt
new file mode 100644
index 0000000000..588de68ad0
--- /dev/null
+++ b/icons/mob/tallborg/dullahan_credit.txt
@@ -0,0 +1,13 @@
+
+Credit:
+these sprites were originally made by:
+- https://github.com/Cecily-C-Catherine
+- Toriate
+and was partly canabalized from meka.
+: for bubberstation
+
+-----------------------
+
+
+the original sprites were recolored and edited by asher-49 and MAK for chompstation, then downstreamed to virgo.
+- animations made by asher-49
diff --git a/icons/mob/tallborg/dullahans64x64.dmi b/icons/mob/tallborg/dullahans64x64.dmi
new file mode 100644
index 0000000000..fe06ae0645
Binary files /dev/null and b/icons/mob/tallborg/dullahans64x64.dmi differ
diff --git a/icons/mob/tallborg/dullahans64x64.txt b/icons/mob/tallborg/dullahans64x64.txt
new file mode 100644
index 0000000000..f064111edc
--- /dev/null
+++ b/icons/mob/tallborg/dullahans64x64.txt
@@ -0,0 +1,3 @@
+the work in this file was done by asher-49 ckey. asher-42 on GitHub.
+
+original design / base sprite artist info is dullahan_credit.txt
\ No newline at end of file
diff --git a/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlAbilities.tsx b/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlAbilities.tsx
index 366c44cb66..5a187f228c 100644
--- a/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlAbilities.tsx
+++ b/tgui/packages/tgui/interfaces/PlayerEffects/PlayerEffectsTabs/ControlAbilities.tsx
@@ -45,6 +45,9 @@ export const ControlAbilities = (props) => {
+
);
};
diff --git a/tgui/public/tgui.html b/tgui/public/tgui.html
index 476d48763c..67f357ef57 100644
--- a/tgui/public/tgui.html
+++ b/tgui/public/tgui.html
@@ -315,6 +315,7 @@
var node = document.createElement('link');
node.type = 'text/css';
node.rel = 'stylesheet';
+ node.crossOrigin = 'anonymous';
node.href = url;
// Temporarily set media to something inapplicable
// to ensure it'll fetch without blocking render
diff --git a/tools/ci/build_tgui.sh b/tools/ci/build_tgui.sh
index 8eea60c083..774e4a838b 100755
--- a/tools/ci/build_tgui.sh
+++ b/tools/ci/build_tgui.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -euo pipefail
-source _build_dependencies.sh
+source dependencies.sh
source ~/.nvm/nvm.sh
nvm use $NODE_VERSION
diff --git a/tools/ci/install_byond.sh b/tools/ci/install_byond.sh
index b11c7387f5..4a688755d3 100755
--- a/tools/ci/install_byond.sh
+++ b/tools/ci/install_byond.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -euo pipefail
-source _build_dependencies.sh
+source dependencies.sh
if [ -d "$HOME/BYOND/byond/bin" ] && grep -Fxq "${BYOND_MAJOR}.${BYOND_MINOR}" $HOME/BYOND/version.txt;
then
@@ -18,4 +18,4 @@ else
make here
echo "$BYOND_MAJOR.$BYOND_MINOR" > "$HOME/BYOND/version.txt"
cd ~/
-fi
\ No newline at end of file
+fi
diff --git a/tools/ci/install_rust_g.sh b/tools/ci/install_rust_g.sh
new file mode 100644
index 0000000000..c237162d8f
--- /dev/null
+++ b/tools/ci/install_rust_g.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+source dependencies.sh
+
+mkdir -p ~/.byond/bin
+wget -nv -O ~/.byond/bin/librust_g.so "https://github.com/tgstation/rust-g/releases/download/$RUST_G_VERSION/librust_g.so"
+chmod +x ~/.byond/bin/librust_g.so
+ldd ~/.byond/bin/librust_g.so
diff --git a/tools/ci/install_spaceman_dmm.sh b/tools/ci/install_spaceman_dmm.sh
index afd89b17b0..d92dca4e95 100755
--- a/tools/ci/install_spaceman_dmm.sh
+++ b/tools/ci/install_spaceman_dmm.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -euo pipefail
-source _build_dependencies.sh
+source dependencies.sh
if [ ! -f ~/$1 ]; then
mkdir -p "$HOME/SpacemanDMM"
@@ -16,4 +16,4 @@ if [ ! -f ~/$1 ]; then
ln -s "$CACHEFILE" ~/$1
fi
-~/$1 --version
\ No newline at end of file
+~/$1 --version
diff --git a/tools/ci/validate_files.sh b/tools/ci/validate_files.sh
index 462df7dfdb..c63aed9037 100755
--- a/tools/ci/validate_files.sh
+++ b/tools/ci/validate_files.sh
@@ -4,7 +4,7 @@ set -euo pipefail
# nb: must be bash to support shopt globstar
shopt -s globstar extglob
-source _build_dependencies.sh
+source dependencies.sh
# ANSI Colors
RED='\033[0;31m'
diff --git a/vorestation.dme b/vorestation.dme
index 331a05f737..e07df198e6 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -39,6 +39,7 @@
#include "code\__defines\appearance.dm"
#include "code\__defines\assemblies.dm"
#include "code\__defines\assert.dm"
+#include "code\__defines\assets.dm"
#include "code\__defines\atmos.dm"
#include "code\__defines\atoms_movable.dm"
#include "code\__defines\belly_modes_vr.dm"
@@ -2023,16 +2024,27 @@
#include "code\modules\asset_cache\asset_cache_client.dm"
#include "code\modules\asset_cache\asset_cache_item.dm"
#include "code\modules\asset_cache\asset_list.dm"
-#include "code\modules\asset_cache\asset_list_items.dm"
-#include "code\modules\asset_cache\assets\chat.dm"
+#include "code\modules\asset_cache\assets\browser.dm"
#include "code\modules\asset_cache\assets\circuits.dm"
+#include "code\modules\asset_cache\assets\cloning.dm"
#include "code\modules\asset_cache\assets\fontawesome.dm"
#include "code\modules\asset_cache\assets\icon_ref_map.dm"
#include "code\modules\asset_cache\assets\jquery.dm"
-#include "code\modules\asset_cache\assets\kitchen_recipes.dm"
+#include "code\modules\asset_cache\assets\ntos.dm"
#include "code\modules\asset_cache\assets\preferences.dm"
#include "code\modules\asset_cache\assets\tgfont.dm"
#include "code\modules\asset_cache\assets\tgui.dm"
+#include "code\modules\asset_cache\assets\spritesheets\chat.dm"
+#include "code\modules\asset_cache\assets\spritesheets\chem_master.dm"
+#include "code\modules\asset_cache\assets\spritesheets\kitchen_recipes.dm"
+#include "code\modules\asset_cache\assets\spritesheets\nanomaps.dm"
+#include "code\modules\asset_cache\assets\spritesheets\pipes.dm"
+#include "code\modules\asset_cache\assets\spritesheets\robot_icons.dm"
+#include "code\modules\asset_cache\assets\spritesheets\stacks.dm"
+#include "code\modules\asset_cache\assets\spritesheets\vending.dm"
+#include "code\modules\asset_cache\assets\spritesheets\vore.dm"
+#include "code\modules\asset_cache\iconforge\batched_spritesheet.dm"
+#include "code\modules\asset_cache\iconforge\universal_icon.dm"
#include "code\modules\asset_cache\transports\asset_transport.dm"
#include "code\modules\asset_cache\transports\webroot_transport.dm"
#include "code\modules\awaymissions\bluespaceartillery.dm"