From 9f84a4e234a4f4e39d76cb688f8332753b0f7493 Mon Sep 17 00:00:00 2001 From: Poojawa Date: Sat, 11 Jul 2026 11:09:59 -0500 Subject: [PATCH] Chem Master QOL and fixes (#19578) * Chem Master QOL and fixes Up-ports Rogue star PRs 309, 359, and 487 addressing issues with the Chem Master Chem master's buffer slashed to 300 units (Bluespace beaker) but gifted the ability to actually keep reagents in the machine to eject a beaker. Overfilling mechanics implemented to prevent wasting reagents since they'd be just deleted otherwise. * HeaderButton2 * fixed header button, trying to figure out nitpicks * Buffer will now display status Craziness. and Copy Pasta for the win. It's also so nice to actually be able to consistently use the dev debug mode for tgui. * Revises some tgui implimentation Gets rid of the beaker tracking entirely from Chem master's buffer. Because... it's the buffer. BeakerContents.tx -> ChemicalContents.tsx to reflect that it controls both now. * Update ChemMasterBuffer.tsx * Alt click and hot swapping containers. Condimaster mode should also protect from food insertion on the regular chem machine. --- code/modules/food/food/snacks.dm | 8 +- .../modules/reagents/machinery/chem_master.dm | 125 +++++++++++++----- .../BorgHypo/BorgHypoRecipeDisplay.tsx | 2 +- .../ChemDispenser/ChemDispenserBeaker.tsx | 2 +- .../ChemMaster/ChemMasterBeaker.tsx | 21 ++- .../ChemMaster/ChemMasterBuffer.tsx | 10 +- .../tgui/interfaces/ChemMaster/index.tsx | 5 +- ...eakerContents.tsx => ChemicalContents.tsx} | 23 +++- 8 files changed, 151 insertions(+), 45 deletions(-) rename tgui/packages/tgui/interfaces/common/{BeakerContents.tsx => ChemicalContents.tsx} (53%) diff --git a/code/modules/food/food/snacks.dm b/code/modules/food/food/snacks.dm index 2f175dfd973..dcd7fb1e623 100644 --- a/code/modules/food/food/snacks.dm +++ b/code/modules/food/food/snacks.dm @@ -885,9 +885,12 @@ /obj/item/reagent_containers/food/snacks/egg/afterattack(obj/O as obj, mob/user as mob, proximity) if(istype(O,/obj/machinery/microwave)) return . = ..() + if((istype(O,/obj/machinery/chem_master)) || (istype(O,/obj/machinery/chemical_dispenser))) + to_chat(user, span_warning("You can't find a good place to crack it into this machine, maybe try a beaker?")) + return if(!(proximity && O.is_open_container())) return - to_chat(user, "You crack \the [src] into \the [O].") + to_chat(user, span_notice("You crack \the [src] into \the [O].")) reagents.trans_to(O, reagents.total_volume) user.drop_from_inventory(src) qdel(src) @@ -5036,6 +5039,9 @@ /obj/item/reagent_containers/food/snacks/siffruit/afterattack(obj/O as obj, mob/user as mob, proximity) if(istype(O,/obj/machinery/microwave)) return ..() + if((istype(O,/obj/machinery/chem_master)) || (istype(O, /obj/machinery/chemical_dispenser))) + to_chat(user, span_warning("You can't find a good place to crack it into this machine, maybe try a beaker?")) + return if(!(proximity && O.is_open_container())) return to_chat(user, span_notice("You tear \the [src]'s sac open, pouring it into \the [O].")) diff --git a/code/modules/reagents/machinery/chem_master.dm b/code/modules/reagents/machinery/chem_master.dm index 529cfcb46ce..6a03f34fdd3 100644 --- a/code/modules/reagents/machinery/chem_master.dm +++ b/code/modules/reagents/machinery/chem_master.dm @@ -11,8 +11,8 @@ idle_power_usage = 20 var/obj/item/reagent_containers/beaker = null var/obj/item/storage/pill_bottle/loaded_pill_bottle = null - var/mode = 0 - var/condi = 0 + var/mode = FALSE //beaker or disposal transfer + var/condi = FALSE //condimaster ? var/useramount = 15 // Last used amount var/pillamount = 10 var/list/bottle_styles @@ -26,7 +26,7 @@ /obj/machinery/chem_master/Initialize(mapload) . = ..() default_apply_parts() - var/datum/reagents/R = new/datum/reagents(900) //Just a huge random number so the buffer should (probably) never dump your reagents. + var/datum/reagents/R = new/datum/reagents(300) //Exactly one bluespace beaker worth of buffer. reagents = R //There should be a nano ui thingy to warn of this. R.my_atom = src @@ -44,37 +44,78 @@ icon_state = "mixer[beaker ? "1" : "0"]" /obj/machinery/chem_master/attackby(obj/item/B as obj, mob/user as mob) - - if(istype(B, /obj/item/reagent_containers/glass) || istype(B, /obj/item/reagent_containers/food)) - - if(src.beaker) - to_chat(user, "\A [beaker] is already loaded into the machine.") - return - src.beaker = B - user.drop_item() - B.loc = src - to_chat(user, "You add \the [B] to the machine.") + if(condi && istype(B, /obj/item/reagent_containers/food)) + if(!beaker) + if(user && Adjacent(user)) + user.drop_from_inventory(B, src) + else + beaker.forceMove(drop_location()) + if(user && Adjacent(user)) + user.drop_from_inventory(B, src) + user.put_in_hands(beaker) + to_chat(user, span_notice("You add \the [B] into the receptical [beaker ? "and swap out [beaker]" : ""].")) + beaker = B update_icon() + return TRUE + + if(istype(B, /obj/item/reagent_containers/glass)) + if(!beaker) + if(user && Adjacent(user)) + user.drop_from_inventory(B, src) + else + beaker.forceMove(drop_location()) + if(user && Adjacent(user)) + user.drop_from_inventory(B, src) + user.put_in_hands(beaker) + to_chat(user, span_notice("You add \the [B] into the receptical [beaker ? "and swap out [beaker]" : ""].")) + beaker = B + update_icon() + return TRUE else if(istype(B, /obj/item/storage/pill_bottle)) + if(!loaded_pill_bottle) + if(user && Adjacent(user)) + user.drop_from_inventory(B, src) + else + loaded_pill_bottle.forceMove(drop_location()) + if(user && Adjacent(user)) + user.drop_from_inventory(B, src) + user.put_in_hands(loaded_pill_bottle) + to_chat(user, span_notice("You add \the [B] into the pill dispenser slot [loaded_pill_bottle ? "and swap out [loaded_pill_bottle]" : ""].")) + loaded_pill_bottle = B + update_icon() + return TRUE - if(src.loaded_pill_bottle) - to_chat(user, "A \the [loaded_pill_bottle] s already loaded into the machine.") - return - - src.loaded_pill_bottle = B - user.drop_item() - B.loc = src - to_chat(user, "You add \the [loaded_pill_bottle] into the dispenser slot.") - - else if(default_unfasten_wrench(user, B, 20)) + if(default_unfasten_wrench(user, B, 20)) return if(default_deconstruction_screwdriver(user, B)) return if(default_deconstruction_crowbar(user, B)) return - return +/obj/machinery/chem_master/click_alt(mob/user) + if(!iscarbon(user)) + return FALSE + take_container(user, user.get_active_hand()) + +/obj/machinery/chem_master/proc/take_container(mob/living/user, obj/item/new_container) + if(!new_container && beaker) + if(user && Adjacent(user)) + user.put_in_hands(beaker) + update_icon() + to_chat(user, span_notice("You take out \the [beaker] from the receptical.")) + beaker = null + return TRUE + else if(!new_container && !beaker && loaded_pill_bottle) + if(user && Adjacent(user)) + user.put_in_hands(loaded_pill_bottle) + update_icon() + to_chat(user, span_notice("You take out \the [loaded_pill_bottle] from the pill dispenser.")) + loaded_pill_bottle = null + return TRUE + else + to_chat(user, span_notice("There's nothing to take out from the receptical.")) + return FALSE /obj/machinery/chem_master/attack_hand(mob/user as mob) if(stat & BROKEN) @@ -108,6 +149,11 @@ data["loaded_pill_bottle_contents_len"] = loaded_pill_bottle.contents.len data["loaded_pill_bottle_storage_slots"] = loaded_pill_bottle.max_storage_space + var/list/buffer_reagents_list = list() + data["buffer_reagents"] = buffer_reagents_list + for(var/datum/reagent/R in reagents.reagent_list) + buffer_reagents_list[++buffer_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "id" = R.id, "description" = R.description) + data["beaker"] = !!beaker if(beaker) var/list/beaker_reagents_list = list() @@ -115,11 +161,6 @@ for(var/datum/reagent/R in beaker.reagents.reagent_list) beaker_reagents_list[++beaker_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "description" = R.description, "id" = R.id) - var/list/buffer_reagents_list = list() - data["buffer_reagents"] = buffer_reagents_list - for(var/datum/reagent/R in reagents.reagent_list) - buffer_reagents_list[++buffer_reagents_list.len] = list("name" = R.name, "volume" = R.volume, "id" = R.id, "description" = R.description) - data["pillsprite"] = pillsprite data["bottlesprite"] = bottlesprite data["mode"] = mode @@ -449,17 +490,39 @@ var/amount = text2num(params["amount"]) if(!id || !amount) return - R.trans_id_to(src, id, amount) + if(reagents && !reagents.get_free_space()) + to_chat(ui.user, span_warning("The reagent buffer is too full!")) + return + var/remaining = amount - reagents.get_free_space() + if(remaining <= 0) + R.trans_id_to(src, id, amount) + else + R.trans_id_to(src, id, reagents.get_free_space()) if("remove") var/id = params["id"] var/amount = text2num(params["amount"]) if(!id || !amount) return if(mode) - reagents.trans_id_to(beaker, id, amount) + if(R && !R.get_free_space()) + to_chat(ui.user, span_warning("\the [beaker.name] is too full!")) + return + var/remaining = amount - R.get_free_space() //figure out if we'd have leftovers + if(remaining <= 0) //No leftovers means we can fill the whole thing + reagents.trans_id_to(beaker, id, amount) + else + reagents.trans_id_to(beaker, id, R.get_free_space()) else reagents.remove_reagent(id, amount) if("eject") + if(!beaker) + return + beaker.forceMove(get_turf(src)) + if(Adjacent(ui.user) && !issilicon(ui.user)) + ui.user.put_in_hands(beaker) + beaker = null + update_icon() + if("ejectandclear") if(!beaker) return beaker.forceMove(get_turf(src)) diff --git a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipeDisplay.tsx b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipeDisplay.tsx index 7145b6fc2ab..c5f54967fb6 100644 --- a/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipeDisplay.tsx +++ b/tgui/packages/tgui/interfaces/BorgHypo/BorgHypoRecipeDisplay.tsx @@ -1,6 +1,6 @@ import { useBackend } from 'tgui/backend'; import { Box, Section, Stack } from 'tgui-core/components'; -import { formatUnits } from '../common/BeakerContents'; +import { formatUnits } from '../common/ChemicalContents'; import type { Data } from './types'; export const BorgHypoRecipeDisplay = (props) => { diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx index 5dedb3acb0b..6892a7c7f89 100644 --- a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx +++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx @@ -1,7 +1,7 @@ import { useBackend } from 'tgui/backend'; import { Box, Button, Section, Stack } from 'tgui-core/components'; -import { BeakerContents } from '../common/BeakerContents'; +import { BeakerContents } from '../common/ChemicalContents'; import { removeAmounts } from './constants'; import type { Data } from './types'; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx index deb3b80a9ec..52a772eb715 100644 --- a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx @@ -2,7 +2,7 @@ import { useBackend } from 'tgui/backend'; import { Box, Button, Section, Stack } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; -import { BeakerContents } from '../common/BeakerContents'; +import { BeakerContents } from '../common/ChemicalContents'; import { modalOpen } from '../common/ComplexModal'; import { transferAmounts } from './constants'; import type { reagent } from './types'; @@ -15,18 +15,33 @@ export const ChemMasterBeaker = (props: { const { act } = useBackend(); const { beaker, beakerReagents, bufferNonEmpty } = props; - const headerButton = bufferNonEmpty ? ( + const headerButton = (<> {bufferNonEmpty ? ( act('eject')} > - Eject and Clear Buffer + Eject and Keep Buffer ) : ( + )} + {bufferNonEmpty ? ( + act('ejectandclear')} + > + Eject and Clear Buffer + + ) : ( + + )} + ); return ( diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx index 17fecfba612..497977855d8 100644 --- a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx @@ -2,7 +2,7 @@ import { useBackend } from 'tgui/backend'; import { Box, Button, Section, Stack } from 'tgui-core/components'; import type { BooleanLike } from 'tgui-core/react'; -import { BeakerContents } from '../common/BeakerContents'; +import { BufferContents } from '../common/ChemicalContents'; import { modalOpen } from '../common/ComplexModal'; import { transferAmounts } from './constants'; import type { reagent } from './types'; @@ -30,9 +30,8 @@ export const ChemMasterBuffer = (props: { } > {bufferReagents.length > 0 ? ( - ( @@ -41,7 +40,6 @@ export const ChemMasterBuffer = (props: { onClick={() => modalOpen('analyze', { idx: i + 1, - beaker: 0, }) } > @@ -92,7 +90,7 @@ export const ChemMasterBuffer = (props: { )} /> ) : ( - Buffer is empty. + The buffer is empty. )} ); diff --git a/tgui/packages/tgui/interfaces/ChemMaster/index.tsx b/tgui/packages/tgui/interfaces/ChemMaster/index.tsx index 1796dcd974e..51053cdecec 100644 --- a/tgui/packages/tgui/interfaces/ChemMaster/index.tsx +++ b/tgui/packages/tgui/interfaces/ChemMaster/index.tsx @@ -35,7 +35,10 @@ export const ChemMaster = (props) => { beakerReagents={beaker_reagents} bufferNonEmpty={buffer_reagents.length > 0} /> - + 0} diff --git a/tgui/packages/tgui/interfaces/common/BeakerContents.tsx b/tgui/packages/tgui/interfaces/common/ChemicalContents.tsx similarity index 53% rename from tgui/packages/tgui/interfaces/common/BeakerContents.tsx rename to tgui/packages/tgui/interfaces/common/ChemicalContents.tsx index 943920f8fb9..d9d9d4fd618 100644 --- a/tgui/packages/tgui/interfaces/common/BeakerContents.tsx +++ b/tgui/packages/tgui/interfaces/common/ChemicalContents.tsx @@ -12,7 +12,7 @@ export const BeakerContents = (props) => { {(!beakerLoaded && No beaker loaded.) || (beakerContents.length === 0 && ( - Beaker is empty. + The beaker is empty. ))} {beakerContents.map((chemical, i) => ( @@ -27,3 +27,24 @@ export const BeakerContents = (props) => { ); }; +/** Like above, but for buffer contents */ +export const BufferContents = (props) => { + const { bufferContents = [], buttons } = props; + return ( + + {(bufferContents.length === 0 && ( + The buffer is empty. + ))} + {bufferContents.map((chemical, i) => ( + + + + {formatUnits(chemical.volume)} of {chemical.name} + + {!!buttons && {buttons(chemical, i)}} + + + ))} + + ); +};