diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm
index 6559f28f26..6bd32c762c 100644
--- a/code/ATMOSPHERICS/atmospherics.dm
+++ b/code/ATMOSPHERICS/atmospherics.dm
@@ -48,6 +48,9 @@ Pipelines + Other Objects -> Pipe network
pipe_color = null
init_dir()
+/obj/machinery/atmospherics/examine_icon()
+ return icon(icon=initial(icon),icon_state=initial(icon_state))
+
// This is used to set up what directions pipes will connect to. Should be called inside New() and whenever a dir changes.
/obj/machinery/atmospherics/proc/init_dir()
return
diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm
index 5abedfe9ac..a199c88622 100644
--- a/code/_helpers/icons.dm
+++ b/code/_helpers/icons.dm
@@ -107,7 +107,7 @@ AngleToHue(hue)
Converts an angle to a hue in the valid range.
RotateHue(hsv, angle)
Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360.
- (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value
+ (Rotating red by 60deg produces yellow.) The result is another HSV or HSVA color with the same saturation and value
as the original, but a different hue.
GrayScale(rgb)
Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string.
@@ -679,7 +679,7 @@ proc/ColorTone(rgb, tone)
var/curstate = A.icon_state || defstate
if(!((noIcon = (!curicon))))
- var/curstates = icon_states(curicon)
+ var/curstates = cached_icon_states(curicon)
if(!(curstate in curstates))
if("" in curstates)
curstate = ""
@@ -689,19 +689,16 @@ proc/ColorTone(rgb, tone)
var/curdir
var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have
- //These should use the parent's direction (most likely)
- if(!A.dir || A.dir == SOUTH)
- curdir = defdir
- else
- curdir = A.dir
+ // Use the requested dir or the atom's current dir
+ curdir = defdir || A.dir
- //Try to remove/optimize this section ASAP, CPU hog.
+ //Try to remove/optimize this section ASAP, CPU hog. //Slightly mitigated by implementing caching using cached_icon_states
//Determines if there's directionals.
if(!noIcon && curdir != SOUTH)
var/exist = FALSE
var/static/list/checkdirs = list(NORTH, EAST, WEST)
for(var/i in checkdirs) //Not using GLOB for a reason.
- if(length(icon_states(icon(curicon, curstate, i))))
+ if(length(cached_icon_states(icon(curicon, curstate, i))))
exist = TRUE
break
if(!exist)
@@ -739,8 +736,8 @@ proc/ColorTone(rgb, tone)
continue
var/current_layer = current.layer
if(current_layer < 0)
- if(current_layer <= -1000)
- return flat
+ //if(current_layer <= -1000)
+ //return flat
current_layer = process_set + A.layer + current_layer / 1000
for(var/p in 1 to layers.len)
@@ -768,7 +765,7 @@ proc/ColorTone(rgb, tone)
curblend = BLEND_OVERLAY
add = icon(I.icon, I.icon_state, base_icon_dir)
else // 'I' is an appearance object.
- add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim)
+ add = getFlatIcon(image(I), I.dir||curdir, curicon, curstate, curblend, FALSE, no_anim)
if(!add)
continue
// Find the new dimensions of the flat icon to fit the added overlay
@@ -899,6 +896,37 @@ proc/ColorTone(rgb, tone)
composite.Blend(icon(I.icon, I.icon_state, I.dir, 1), ICON_OVERLAY)
return composite
+GLOBAL_LIST_EMPTY(icon_state_lists)
+/proc/cached_icon_states(var/icon/I)
+ if(!I)
+ return list()
+ var/key = I
+ var/returnlist = GLOB.icon_state_lists[key]
+ if(!returnlist)
+ returnlist = icon_states(I)
+ if(isfile(I)) // It's something that will stick around
+ GLOB.icon_state_lists[key] = returnlist
+ return returnlist
+
+/proc/expire_states_cache(var/key)
+ if(GLOB.icon_state_lists[key])
+ GLOB.icon_state_lists -= key
+ return TRUE
+ return FALSE
+
+GLOBAL_LIST_EMPTY(cached_examine_icons)
+/proc/set_cached_examine_icon(var/atom/A, var/icon/I, var/expiry = 12000)
+ GLOB.cached_examine_icons[weakref(A)] = I
+ if(expiry)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/uncache_examine_icon, weakref(A)), expiry, TIMER_UNIQUE)
+
+/proc/get_cached_examine_icon(var/atom/A)
+ var/weakref/WR = weakref(A)
+ return GLOB.cached_examine_icons[WR]
+
+/proc/uncache_examine_icon(var/weakref/WR)
+ GLOB.cached_examine_icons -= WR
+
proc/adjust_brightness(var/color, var/value)
if (!color) return "#FFFFFF"
if (!value) return color
diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm
index 73ae811193..d4400da8df 100644
--- a/code/_helpers/mobs.dm
+++ b/code/_helpers/mobs.dm
@@ -94,7 +94,7 @@ proc/age2agedescription(age)
else return "unknown"
/proc/RoundHealth(health)
- var/list/icon_states = icon_states(ingame_hud_med)
+ var/list/icon_states = cached_icon_states(ingame_hud_med)
for(var/icon_state in icon_states)
if(health >= text2num(icon_state))
return icon_state
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 5f8ccb3391..7d39676d51 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -192,6 +192,10 @@
return output
+// Don't make these call bicon or anything, these are what bicon uses. They need to return an icon.
+/atom/proc/examine_icon()
+ return icon(icon=src.icon, icon_state=src.icon_state, dir=SOUTH, frame=1, moving=0)
+
// called by mobs when e.g. having the atom as their machine, pulledby, loc (AKA mob being inside the atom) or buckled var set.
// see code/modules/mob/mob_movement.dm for more.
/atom/proc/relaymove()
diff --git a/code/game/objects/items/weapons/storage/wallets.dm b/code/game/objects/items/weapons/storage/wallets.dm
index 95db86460e..a668075068 100644
--- a/code/game/objects/items/weapons/storage/wallets.dm
+++ b/code/game/objects/items/weapons/storage/wallets.dm
@@ -63,7 +63,7 @@
overlays.Cut()
if(front_id)
var/tiny_state = "id-generic"
- if("id-"+front_id.icon_state in icon_states(icon))
+ if("id-"+front_id.icon_state in cached_icon_states(icon))
tiny_state = "id-"+front_id.icon_state
var/image/tiny_image = new/image(icon, icon_state = tiny_state)
tiny_image.appearance_flags = RESET_COLOR
diff --git a/code/game/objects/structures/barsign.dm b/code/game/objects/structures/barsign.dm
index cd1c448b2f..e54c75eebb 100644
--- a/code/game/objects/structures/barsign.dm
+++ b/code/game/objects/structures/barsign.dm
@@ -6,7 +6,7 @@
var/cult = 0
/obj/structure/sign/double/barsign/proc/get_valid_states(initial=1)
- . = icon_states(icon)
+ . = cached_icon_states(icon)
. -= "on"
. -= "narsiebistro"
. -= "empty"
diff --git a/code/game/objects/structures/cliff.dm b/code/game/objects/structures/cliff.dm
index c4caf4b7ab..66fb945988 100644
--- a/code/game/objects/structures/cliff.dm
+++ b/code/game/objects/structures/cliff.dm
@@ -114,7 +114,7 @@ two tiles on initialization, and which way a cliff is facing may change during m
var/subtraction_icon_state = "[icon_state]-subtract"
var/cache_string = "[icon_state]_[T.icon]_[T.icon_state]"
- if(T && subtraction_icon_state in icon_states(icon))
+ if(T && subtraction_icon_state in cached_icon_states(icon))
cut_overlays()
// If we've made the same icon before, just recycle it.
if(cache_string in GLOB.cliff_icon_cache)
diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm
index 3df62610c9..2434d33e89 100644
--- a/code/game/objects/structures/window.dm
+++ b/code/game/objects/structures/window.dm
@@ -47,6 +47,9 @@
else
. += "There is a thick layer of silicate covering it."
+/obj/structure/window/examine_icon()
+ return icon(icon=initial(icon),icon_state=initial(icon_state))
+
/obj/structure/window/take_damage(var/damage = 0, var/sound_effect = 1)
var/initialhealth = health
diff --git a/code/game/turfs/simulated/wall_icon.dm b/code/game/turfs/simulated/wall_icon.dm
index 5bf41c4a5f..dd49744a8f 100644
--- a/code/game/turfs/simulated/wall_icon.dm
+++ b/code/game/turfs/simulated/wall_icon.dm
@@ -67,7 +67,7 @@
I.color = reinf_material.icon_colour
add_overlay(I)
else
- if("[reinf_material.icon_reinf]0" in icon_states('icons/turf/wall_masks.dmi'))
+ if("[reinf_material.icon_reinf]0" in cached_icon_states('icons/turf/wall_masks.dmi'))
// Directional icon
for(var/i = 1 to 4)
I = image('icons/turf/wall_masks.dmi', "[reinf_material.icon_reinf][wall_connections[i]]", dir = 1<<(i-1))
diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm
index add0b3b11d..1fc49d58a9 100644
--- a/code/game/turfs/simulated/walls.dm
+++ b/code/game/turfs/simulated/walls.dm
@@ -46,6 +46,9 @@
dismantle_wall(null,null,1)
..()
+/turf/simulated/wall/examine_icon()
+ return icon(icon=initial(icon), icon_state=initial(icon_state))
+
/turf/simulated/wall/process()
// Calling parent will kill processing
if(!radiate())
diff --git a/code/modules/client/asset_cache.dm b/code/modules/client/asset_cache.dm
index 5580c7fd98..ed6439d3cd 100644
--- a/code/modules/client/asset_cache.dm
+++ b/code/modules/client/asset_cache.dm
@@ -204,12 +204,12 @@ You can set verify to TRUE if you want send() to sleep until the client has the
directions = list(SOUTH)
var/sprites = list()
- for (var/icon_state_name in icon_states(I))
+ for (var/icon_state_name in cached_icon_states(I))
for (var/direction in directions)
var/suffix = (directions.len > 1) ? "-[dir2text(direction)]" : ""
var/sprite_name = "[prefix][icon_state_name][suffix]"
var/icon/sprite = icon(I, icon_state=icon_state_name, dir=direction, frame=1, moving=FALSE)
- if (!sprite || !length(icon_states(sprite))) // that direction or state doesn't exist
+ if (!sprite || !length(cached_icon_states(sprite))) // that direction or state doesn't exist
continue
sprites[sprite_name] = sprite
return sprites
diff --git a/code/modules/client/preference_setup/general/03_body.dm b/code/modules/client/preference_setup/general/03_body.dm
index adb52397ff..3208179b91 100644
--- a/code/modules/client/preference_setup/general/03_body.dm
+++ b/code/modules/client/preference_setup/general/03_body.dm
@@ -841,7 +841,7 @@ var/global/list/valid_bloodtypes = list("A+", "A-", "B+", "B-", "AB+", "AB-", "O
dat += "
[current_species.blurb] | "
//vorestation edit end
dat += ""
- if("preview" in icon_states(current_species.icobase))
+ if("preview" in cached_icon_states(current_species.icobase))
usr << browse_rsc(icon(current_species.icobase,"preview"), "species_preview_[current_species.name].png")
dat += "
"
dat += "Language: [current_species.species_language] "
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 283406d6ba..eff875ee8d 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -805,7 +805,7 @@
//autodetect rollability
if(rolled_down < 0)
- if(("[worn_state]_d_s" in icon_states(icon)) || ("[worn_state]_s" in icon_states(rolled_down_icon)) || ("[worn_state]_d_s" in icon_states(icon_override)))
+ if(("[worn_state]_d_s" in cached_icon_states(icon)) || ("[worn_state]_s" in cached_icon_states(rolled_down_icon)) || ("[worn_state]_d_s" in cached_icon_states(icon_override)))
rolled_down = 0
if(rolled_down == -1)
@@ -842,11 +842,11 @@
under_icon = sprite_sheets[H.species.get_bodytype(H)]
else if(item_icons && item_icons[slot_w_uniform_str])
under_icon = item_icons[slot_w_uniform_str]
- else if ("[worn_state]_s" in icon_states(rolled_down_icon))
+ else if ("[worn_state]_s" in cached_icon_states(rolled_down_icon))
under_icon = rolled_down_icon
// The _s is because the icon update procs append it.
- if((under_icon == rolled_down_icon && "[worn_state]_s" in icon_states(under_icon)) || ("[worn_state]_d_s" in icon_states(under_icon)))
+ if((under_icon == rolled_down_icon && "[worn_state]_s" in cached_icon_states(under_icon)) || ("[worn_state]_d_s" in cached_icon_states(under_icon)))
if(rolled_down != 1)
rolled_down = 0
else
@@ -865,13 +865,13 @@
under_icon = sprite_sheets[H.species.get_bodytype(H)]
else if(item_icons && item_icons[slot_w_uniform_str])
under_icon = item_icons[slot_w_uniform_str]
- else if ("[worn_state]_s" in icon_states(rolled_down_sleeves_icon))
+ else if ("[worn_state]_s" in cached_icon_states(rolled_down_sleeves_icon))
under_icon = rolled_down_sleeves_icon
else if(index)
under_icon = new /icon("[INV_W_UNIFORM_DEF_ICON]_[index].dmi")
// The _s is because the icon update procs append it.
- if((under_icon == rolled_down_sleeves_icon && "[worn_state]_s" in icon_states(under_icon)) || ("[worn_state]_r_s" in icon_states(under_icon)))
+ if((under_icon == rolled_down_sleeves_icon && "[worn_state]_s" in cached_icon_states(under_icon)) || ("[worn_state]_r_s" in cached_icon_states(under_icon)))
if(rolled_sleeves != 1)
rolled_sleeves = 0
else
@@ -955,7 +955,7 @@
if(rolled_down)
body_parts_covered = initial(body_parts_covered)
body_parts_covered &= ~(UPPER_TORSO|ARMS)
- if("[worn_state]_s" in icon_states(rolled_down_icon))
+ if("[worn_state]_s" in cached_icon_states(rolled_down_icon))
icon_override = rolled_down_icon
item_state_slots[slot_w_uniform_str] = "[worn_state]"
else
@@ -988,7 +988,7 @@
rolled_sleeves = !rolled_sleeves
if(rolled_sleeves)
body_parts_covered &= ~(ARMS)
- if("[worn_state]_s" in icon_states(rolled_down_sleeves_icon))
+ if("[worn_state]_s" in cached_icon_states(rolled_down_sleeves_icon))
icon_override = rolled_down_sleeves_icon
item_state_slots[slot_w_uniform_str] = "[worn_state]"
else
diff --git a/code/modules/clothing/clothing_vr.dm b/code/modules/clothing/clothing_vr.dm
index 1fb7d26548..470cb94c06 100644
--- a/code/modules/clothing/clothing_vr.dm
+++ b/code/modules/clothing/clothing_vr.dm
@@ -134,7 +134,7 @@
var/mob/living/carbon/human/H = user
if(isTaurTail(H.tail_style))
var/datum/sprite_accessory/tail/taur/taurtail = H.tail_style
- if(taurtail.suit_sprites && (get_worn_icon_state(slot_wear_suit_str) in icon_states(taurtail.suit_sprites)))
+ if(taurtail.suit_sprites && (get_worn_icon_state(slot_wear_suit_str) in cached_icon_states(taurtail.suit_sprites)))
icon_override = taurtail.suit_sprites
normalize = FALSE
taurized = TRUE
diff --git a/code/modules/clothing/under/accessories/accessory.dm b/code/modules/clothing/under/accessories/accessory.dm
index 57fe80cd76..6aed5daf39 100644
--- a/code/modules/clothing/under/accessories/accessory.dm
+++ b/code/modules/clothing/under/accessories/accessory.dm
@@ -24,7 +24,7 @@
if(!inv_overlay)
var/tmp_icon_state = "[overlay_state? "[overlay_state]" : "[icon_state]"]"
if(icon_override)
- if("[tmp_icon_state]_tie" in icon_states(icon_override))
+ if("[tmp_icon_state]_tie" in cached_icon_states(icon_override))
tmp_icon_state = "[tmp_icon_state]_tie"
inv_overlay = image(icon = icon_override, icon_state = tmp_icon_state, dir = SOUTH)
else
@@ -48,7 +48,7 @@
tmp_icon_state = on_rolled["rolled"]
if(icon_override)
- if("[tmp_icon_state]_mob" in icon_states(icon_override))
+ if("[tmp_icon_state]_mob" in cached_icon_states(icon_override))
tmp_icon_state = "[tmp_icon_state]_mob"
mob_overlay = image("icon" = icon_override, "icon_state" = "[tmp_icon_state]")
else if(wearer && sprite_sheets[wearer.species.get_bodytype(wearer)]) //Teshari can finally into webbing, too!
diff --git a/code/modules/clothing/under/accessories/lockets.dm b/code/modules/clothing/under/accessories/lockets.dm
index ca859addb5..153df0d584 100644
--- a/code/modules/clothing/under/accessories/lockets.dm
+++ b/code/modules/clothing/under/accessories/lockets.dm
@@ -13,7 +13,7 @@
if(!base_icon)
base_icon = icon_state
- if(!("[base_icon]_open" in icon_states(icon)))
+ if(!("[base_icon]_open" in cached_icon_states(icon)))
to_chat(user, "\The [src] doesn't seem to open.")
return
diff --git a/code/modules/customitems/item_spawning.dm b/code/modules/customitems/item_spawning.dm
index b87183c385..5e85c0123c 100644
--- a/code/modules/customitems/item_spawning.dm
+++ b/code/modules/customitems/item_spawning.dm
@@ -78,7 +78,7 @@
var/list/new_item_icons = list()
var/list/new_item_state_slots = list()
- var/list/available_states = icon_states(CUSTOM_ITEM_MOB)
+ var/list/available_states = cached_icon_states(CUSTOM_ITEM_MOB)
//If l_hand or r_hand are not present, preserve them using item_icons/item_state_slots
//Then use icon_override to make every other slot use the custom sprites by default.
diff --git a/code/modules/examine/examine.dm b/code/modules/examine/examine.dm
index 6e2df0b1fa..f49c672bca 100644
--- a/code/modules/examine/examine.dm
+++ b/code/modules/examine/examine.dm
@@ -5,7 +5,7 @@
This means that this file can be unchecked, along with the other examine files, and can be removed entirely with no effort.
*/
-#define EXAMINE_PANEL_PADDING " "
+#define EXAMINE_PANEL_PADDING " "
/atom/
var/description_info = null //Helpful blue text.
@@ -56,7 +56,7 @@
description_holders["interactions"] = A.get_description_interaction()
description_holders["name"] = "[A.name]"
- description_holders["icon"] = "\icon[A]"
+ description_holders["icon"] = "\icon[A.examine_icon()]"
description_holders["desc"] = A.desc
/mob/Stat()
diff --git a/code/modules/food/drinkingglass/drinkingglass.dm b/code/modules/food/drinkingglass/drinkingglass.dm
index 928c24954b..c2c5dc4219 100644
--- a/code/modules/food/drinkingglass/drinkingglass.dm
+++ b/code/modules/food/drinkingglass/drinkingglass.dm
@@ -74,9 +74,9 @@
update_icon()
/obj/item/weapon/reagent_containers/food/drinks/glass2/proc/can_add_extra(obj/item/weapon/glass_extra/GE)
- if(!("[base_icon]_[GE.glass_addition]left" in icon_states(icon))) //VOREStation Edit
+ if(!("[base_icon]_[GE.glass_addition]left" in cached_icon_states(icon))) //VOREStation Edit
return 0
- if(!("[base_icon]_[GE.glass_addition]right" in icon_states(icon))) //VOREStation Edit
+ if(!("[base_icon]_[GE.glass_addition]right" in cached_icon_states(icon))) //VOREStation Edit
return 0
return 1
@@ -106,9 +106,9 @@
over_liquid |= "[base_icon][amnt]_fizz"
for(var/S in R.glass_special)
- if("[base_icon]_[S]" in icon_states(icon)) //VOREStation Edit
+ if("[base_icon]_[S]" in cached_icon_states(icon)) //VOREStation Edit
under_liquid |= "[base_icon]_[S]"
- else if("[base_icon][amnt]_[S]" in icon_states(icon)) //VOREStation Edit
+ else if("[base_icon][amnt]_[S]" in cached_icon_states(icon)) //VOREStation Edit
over_liquid |= "[base_icon][amnt]_[S]"
for(var/k in under_liquid)
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index 982f44e794..90afa7f416 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -141,7 +141,7 @@
var/image/fruit_base = image('icons/obj/hydroponics_products.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]-product")
fruit_base.color = "[seed.get_trait(TRAIT_PRODUCT_COLOUR)]"
plant_icon.overlays |= fruit_base
- if("[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf" in icon_states('icons/obj/hydroponics_products.dmi'))
+ if("[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf" in cached_icon_states('icons/obj/hydroponics_products.dmi'))
var/image/fruit_leaves = image('icons/obj/hydroponics_products.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf")
fruit_leaves.color = "[seed.get_trait(TRAIT_PLANT_COLOUR)]"
plant_icon.overlays |= fruit_leaves
diff --git a/code/modules/hydroponics/seed_controller.dm b/code/modules/hydroponics/seed_controller.dm
index 1ca42f4c60..28315b361a 100644
--- a/code/modules/hydroponics/seed_controller.dm
+++ b/code/modules/hydroponics/seed_controller.dm
@@ -53,7 +53,7 @@ var/global/datum/controller/plants/plant_controller // Set in New().
/datum/controller/plants/proc/setup()
// Build the icon lists.
- for(var/icostate in icon_states('icons/obj/hydroponics_growing.dmi'))
+ for(var/icostate in cached_icon_states('icons/obj/hydroponics_growing.dmi'))
var/split = findtext(icostate,"-")
if(!split)
// invalid icon_state
@@ -71,7 +71,7 @@ var/global/datum/controller/plants/plant_controller // Set in New().
if(!(base in GLOB.forbidden_plant_growth_sprites))
accessible_plant_sprites[base] = ikey
- for(var/icostate in icon_states('icons/obj/hydroponics_products.dmi'))
+ for(var/icostate in cached_icon_states('icons/obj/hydroponics_products.dmi'))
var/split = findtext(icostate,"-")
var/base = copytext(icostate,1,split)
if(split)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index e20d3740c9..349859029f 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -152,6 +152,13 @@
if(new_stat != DEAD)
CRASH("It is best if observers stay dead, thank you.")
+/mob/observer/dead/examine_icon()
+ var/icon/I = get_cached_examine_icon(src)
+ if(!I)
+ I = getFlatIcon(src, defdir = SOUTH, no_anim = TRUE)
+ set_cached_examine_icon(src, I, 200 SECONDS)
+ return I
+
/*
Transfer_mind is there to check if mob is being deleted/not going to have a body.
Works together with spawning an observer, noted above.
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index f7ee2d9520..9687bde18f 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1596,6 +1596,13 @@
else
layer = HIDING_LAYER
+/mob/living/carbon/human/examine_icon()
+ var/icon/I = get_cached_examine_icon(src)
+ if(!I)
+ I = getFlatIcon(src, defdir = SOUTH, no_anim = TRUE)
+ set_cached_examine_icon(src, I, 50 SECONDS)
+ return I
+
/mob/living/carbon/human/proc/get_display_species()
//Shows species in tooltip
if(src.custom_species) //VOREStation Add
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 963ad4cd38..2903bf604f 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -321,7 +321,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon()
base_icon.MapColors(rgb(tone[1],0,0),rgb(0,tone[2],0),rgb(0,0,tone[3]))
//Handle husk overlay.
- if(husk && ("overlay_husk" in icon_states(species.icobase)))
+ if(husk && ("overlay_husk" in cached_icon_states(species.icobase)))
var/icon/mask = new(base_icon)
var/icon/husk_over = new(species.icobase,"overlay_husk")
mask.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,1, 0,0,0,0)
diff --git a/code/modules/mob/new_player/login.dm b/code/modules/mob/new_player/login.dm
index 57a9574f11..fb7f28d112 100644
--- a/code/modules/mob/new_player/login.dm
+++ b/code/modules/mob/new_player/login.dm
@@ -11,7 +11,7 @@ var/obj/effect/lobby_image = new /obj/effect/lobby_image
/obj/effect/lobby_image/Initialize()
icon = using_map.lobby_icon
- var/known_icon_states = icon_states(icon)
+ var/known_icon_states = cached_icon_states(icon)
for(var/lobby_screen in using_map.lobby_screens)
if(!(lobby_screen in known_icon_states))
error("Lobby screen '[lobby_screen]' did not exist in the icon set [icon].")
diff --git a/code/modules/projectiles/ammunition.dm b/code/modules/projectiles/ammunition.dm
index b07cacd92b..01f4e6003a 100644
--- a/code/modules/projectiles/ammunition.dm
+++ b/code/modules/projectiles/ammunition.dm
@@ -201,7 +201,7 @@
/proc/magazine_icondata_cache_add(var/obj/item/ammo_magazine/M)
var/list/icon_keys = list()
var/list/ammo_states = list()
- var/list/states = icon_states(M.icon)
+ var/list/states = cached_icon_states(M.icon)
for(var/i = 0, i <= M.max_ammo, i++)
var/ammo_state = "[M.icon_state]-[i]"
if(ammo_state in states)
diff --git a/code/modules/tables/tables.dm b/code/modules/tables/tables.dm
index f853e224fc..249c84be73 100644
--- a/code/modules/tables/tables.dm
+++ b/code/modules/tables/tables.dm
@@ -30,6 +30,9 @@ var/list/table_icon_cache = list()
var/item_place = 1 //allows items to be placed on the table, but not on benches.
+/obj/structure/table/examine_icon()
+ return icon(icon=initial(icon), icon_state=initial(icon_state)) //Basically the map preview version
+
/obj/structure/table/proc/update_material()
var/old_maxhealth = maxhealth
if(!material)
diff --git a/code/modules/vchat/vchat_client.dm b/code/modules/vchat/vchat_client.dm
index 6e0ac509b0..8456c5225f 100644
--- a/code/modules/vchat/vchat_client.dm
+++ b/code/modules/vchat/vchat_client.dm
@@ -266,31 +266,45 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of ic
var/list/partial = splittext(iconData, "{")
return replacetext(copytext(partial[2], 3, -5), "\n", "")
+/proc/expire_bicon_cache(key)
+ if(GLOB.bicon_cache[key])
+ GLOB.bicon_cache -= key
+ return TRUE
+ return FALSE
+
+GLOBAL_LIST_EMPTY(bicon_cache) // Cache of the tag results, not the icons
/proc/bicon(var/obj, var/use_class = 1, var/custom_classes = "")
var/class = use_class ? "class='icon misc [custom_classes]'" : null
- if (!obj)
+ if(!obj)
return
- var/static/list/bicon_cache = list()
- if (isicon(obj))
- //Icon refs get reused all the time especially on temporarily made ones like chat tags, too difficult to cache.
- //if (!bicon_cache["\ref[obj]"]) // Doesn't exist yet, make it.
- //bicon_cache["\ref[obj]"] = icon2base64(obj)
-
+ // Try to avoid passing bicon an /icon directly. It is better to pass it an atom so it can cache.
+ if(isicon(obj)) // Passed an icon directly, nothing to cache-key on, as icon refs get reused *often*
return " "
// Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with.
var/atom/A = obj
- var/key = "[istype(A.icon, /icon) ? "\ref[A.icon]" : A.icon]:[A.icon_state]"
- if (!bicon_cache[key]) // Doesn't exist, make it.
- var/icon/I = icon(A.icon, A.icon_state, SOUTH, 1)
- if (ishuman(obj))
- I = getFlatIcon(obj) //Ugly
- bicon_cache[key] = icon2base64(I, key)
+ var/key
+ var/changes_often = ishuman(A) || isobserver(A) // If this ends up with more, move it into a proc or var on atom.
+
+ if(changes_often)
+ key = "\ref[A]"
+ else
+ key = "[istype(A.icon, /icon) ? "\ref[A.icon]" : A.icon]:[A.icon_state]"
+
+ var/base64 = GLOB.bicon_cache[key]
+ // Non-human atom, no cache
+ if(!base64) // Doesn't exist, make it.
+ base64 = icon2base64(A.examine_icon(), key)
+ GLOB.bicon_cache[key] = base64
+ if(changes_often)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/expire_bicon_cache, key), 50 SECONDS, TIMER_UNIQUE)
+
+ // May add a class to the img tag created by bicon
if(use_class)
class = "class='icon [A.icon_state] [custom_classes]'"
- return " "
+ return " "
//Checks if the message content is a valid to_chat message
/proc/is_valid_tochat_message(message)
|