SmartFridge Fixes (#21957)

- moves smartfridge icon generation to ui_static_data and adds a cache
(inspired by / ported in part from tgstation/tgstation#48394 )
- because we cache it, we can now gFI the items and so we fix certain
things not having icons
- bottlemaxxers stay winning, etc.

i actually tested this pr and it works great, upvotes to the left

<img width="1197" height="749" alt="image"
src="https://github.com/user-attachments/assets/7e41dce0-47aa-4fd2-9e18-cc17758a53de"
/>
This commit is contained in:
Wildkins
2026-03-02 04:59:22 -05:00
committed by GitHub
parent 476e3081e0
commit c7cbbfbfb8
3 changed files with 162 additions and 108 deletions
+60 -25
View File
@@ -43,6 +43,8 @@
var/datum/wires/smartfridge/wires = null
atmos_canpass = CANPASS_DENSITY
VAR_PRIVATE/static/list/base64_cache = list()
/obj/machinery/smartfridge/secure
is_secure = 1
@@ -95,6 +97,7 @@
for(var/obj/item/reagent_containers/food/snacks/grown/g in contents)
item_quants[g.name]++
update_overlays()
update_static_data_for_all_viewers()
/obj/machinery/smartfridge/Initialize()
. = ..()
@@ -230,6 +233,10 @@
if(S.on_dry(src)) //Drying rack keeps the item but changes the name. This prevents pre-dried item lingering in the UI as vendable
item_quants[S.name]++
item_quants[old_name]--
if(item_quants[old_name] <= 0 || item_quants[S.name] == 1)
update_static_data_for_all_viewers()
if(item_quants[old_name] <= 0)
item_quants -= old_name
return
/obj/machinery/smartfridge/drying_rack/update_overlays()
@@ -360,25 +367,32 @@
user.remove_from_mob(attacking_item)
attacking_item.forceMove(src)
item_quants[attacking_item.name]++
if(item_quants[attacking_item.name] == 1)
update_static_data_for_all_viewers()
user.visible_message("<b>[user]</b> adds \a [attacking_item] to [src].", SPAN_NOTICE("You add [attacking_item] to [src]."))
update_overlays()
return
return TRUE
if(istype(attacking_item, /obj/item/storage))
var/obj/item/storage/P = attacking_item
var/plants_loaded = 0
var/update_static_data = FALSE
for(var/obj/G in P.contents)
if(accept_check(G))
if(length(contents) >= max_n_of_items)
break
P.remove_from_storage(G,src)
item_quants[G.name]++
if(item_quants[G.name] == 1)
update_static_data = TRUE
plants_loaded++
if(plants_loaded)
user.visible_message("<b>[user]</b> loads [src] with [P].", SPAN_NOTICE("You load [src] with [P]."))
if(length(P.contents) > 0)
to_chat(user, SPAN_NOTICE("Some items are refused."))
update_overlays()
if(update_static_data)
update_static_data_for_all_viewers()
return TRUE
to_chat(user, SPAN_NOTICE("[src] smartly refuses [attacking_item]."))
return TRUE
@@ -416,43 +430,55 @@
/obj/machinery/smartfridge/ui_data(mob/user)
var/list/data = list()
data["contents"] = null
data["electrified"] = seconds_electrified > 0
data["shoot_inventory"] = shoot_inventory
data["locked"] = locked
data["secure"] = is_secure
data["sort_alphabetically"] = ui_sort_alphabetically
var/list/items = list()
for (var/i = 1 to length(item_quants))
var/list/stocks
for (var/i in 1 to length(item_quants))
var/K = item_quants[i]
var/count = item_quants[K]
if(count > 0)
LAZYADDASSOC(stocks, capitalize(K), count)
var/obj/item/item_used = null
for (var/obj/item/O in contents)
if(O.name == K)
item_used = O
break
data["stocks"] = stocks
var/icon/item_icon = null
if(item_used)
if(istype(item_used, /obj/item/seeds))
var/obj/item/seeds/S = item_used
item_icon = S.update_appearance(TRUE)
else
item_icon = new /icon(item_used.icon, item_used.icon_state)
return data
var/final_icon = null
if(item_icon)
final_icon = icon2base64(item_icon)
/obj/machinery/smartfridge/ui_static_data(mob/user)
var/list/data = list()
data["contents"] = null
items.Add(list(list(
"display_name" = capitalize(K),
"vend" = i,
"quantity" = count,
"icon" = final_icon,
)))
var/list/items = list()
for (var/i = 1 to length(item_quants))
var/K = item_quants[i]
var/obj/item/item_used = null
for (var/obj/item/O in contents - component_parts)
if(O.name == K)
item_used = O
break
if(!item_used)
continue
var/final_icon = base64_cache["[K]_[item_used.type]_[item_used.icon_state]"]
if(!final_icon)
var/icon/item_icon
if(istype(item_used, /obj/item/seeds))
var/obj/item/seeds/S = item_used
item_icon = S.update_appearance(TRUE)
else
item_icon = getFlatIcon(item_used)
final_icon = icon2base64(item_icon)
base64_cache["[K]_[item_used.type]_[item_used.icon_state]"] = final_icon
items.Add(list(list(
"display_name" = capitalize(K),
"vend" = i,
"icon" = final_icon,
)))
if(length(items) > 0)
data["contents"] = items
@@ -497,6 +523,10 @@
update_overlays()
if(i <= 0)
break
if(item_quants[K] <= 0)
update_static_data_for_all_viewers()
item_quants -= K
if("switch_sort_alphabetically")
ui_sort_alphabetically = !ui_sort_alphabetically
@@ -510,6 +540,7 @@
for (var/O in item_quants)
if(item_quants[O] <= 0) //Try to use a record that actually has something to dump.
item_quants -= O
continue
item_quants[O]--
@@ -518,6 +549,10 @@
T.forceMove(loc)
throw_item = T
break
if(item_quants[O] <= 0)
update_static_data_for_all_viewers()
item_quants -= O
break
if(!throw_item)
return FALSE
@@ -0,0 +1,14 @@
# Your name.
author: JohnWildkins
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- refactor: "Move smartfridge icon generation to static data and cache results to improve performance."
- bugfix: "Fix chemical bottles not having proper colors in SmartFridge UI."
+88 -83
View File
@@ -5,6 +5,7 @@ import { Window } from '../layouts';
export type FridgeData = {
contents: Item[];
stocks: { [display_name: string]: number };
seconds_electrified: BooleanLike;
shoot_inventory: BooleanLike;
locked: number; // goes to -1
@@ -15,7 +16,6 @@ export type FridgeData = {
type Item = {
display_name: string;
vend: number;
quantity: number;
icon?: string | null;
};
@@ -87,93 +87,98 @@ export const ContentsWindow = (props, context) => {
})
: itemList;
const itemStock = (item: Item) => data.stocks[item.display_name] || 0;
return (
<Section>
<Box>
{itemListSorted.map((item) => (
<Box
key={`${item.vend}-${item.display_name}`}
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '4px 0',
}}
>
{/* Item icon */}
{item.icon ? (
{itemListSorted.map((item) => {
const quantity = itemStock(item);
return (
<Box
key={`${item.vend}-${item.display_name}`}
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '4px 0',
}}
>
{/* Item icon */}
{item.icon ? (
<Box
as="img"
src={`data:image/png;base64,${item.icon}`}
style={{ width: '32px', height: '32px', flexShrink: 0 }}
/>
) : null}
{/* Name stuff */}
<Box
as="img"
src={`data:image/png;base64,${item.icon}`}
style={{ width: '32px', height: '32px', flexShrink: 0 }}
/>
) : null}
{/* Name stuff */}
<Box
style={{
flex: 2,
minWidth: 0,
whiteSpace: 'normal',
overflowWrap: 'break-word',
wordBreak: 'break-word',
}}
>
{item.display_name}
style={{
flex: 2,
minWidth: 0,
whiteSpace: 'normal',
overflowWrap: 'break-word',
wordBreak: 'break-word',
}}
>
{item.display_name}
</Box>
{/* Amount */}x{quantity}
{/* Vend buttons */}
<Box
style={{
flex: 1.2,
minWidth: 0,
whiteSpace: 'normal',
overflowWrap: 'break-word',
wordBreak: 'break-word',
}}
>
{quantity > 0 && (
<Button
content="x1"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 1 })
}
/>
)}
{quantity > 5 && (
<Button
content="x5"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 5 })
}
/>
)}
{quantity > 10 && (
<Button
content="x10"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 10 })
}
/>
)}
{quantity > 25 && (
<Button
content="x25"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 25 })
}
/>
)}
</Box>
</Box>
{/* Amount */}x{item.quantity}
{/* Vend buttons */}
<Box
style={{
flex: 1.2,
minWidth: 0,
whiteSpace: 'normal',
overflowWrap: 'break-word',
wordBreak: 'break-word',
}}
>
{item.quantity > 0 && (
<Button
content="x1"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 1 })
}
/>
)}
{item.quantity > 5 && (
<Button
content="x5"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 5 })
}
/>
)}
{item.quantity > 10 && (
<Button
content="x10"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 10 })
}
/>
)}
{item.quantity > 25 && (
<Button
content="x25"
icon="arrow-alt-circle-down"
width="50px"
onClick={() =>
act('vendItem', { vendItem: item.vend, amount: 25 })
}
/>
)}
</Box>
</Box>
))}
);
})}
</Box>
</Section>
);