diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index c489edf88e..fd6204a8e3 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -973,10 +973,8 @@ for(var/A in cached_reagents) var/datum/reagent/R = A if (R.type == reagent) - if((total_volume - amount) <= 0)//Because this can result in 0, I don't want it to crash. - pH = REAGENT_NORMAL_PH //In practice this is really confusing and players feel like it randomly melts their beakers, but I'm not sure how else to handle it. We'll see how it goes and I can remove this if it confuses people. - else if (!ignore_pH) + if(!ignore_pH) //if (((pH > R.pH) && (pH <= 7)) || ((pH < R.pH) && (pH >= 7))) pH = (((pH - R.pH) / total_volume) * amount) + pH if(istype(my_atom, /obj/item/reagent_containers/)) @@ -987,6 +985,8 @@ amount = clamp(amount, 0, R.volume) R.volume -= amount update_total() + if(total_volume <= 0)//Because this can result in 0, I don't want it to crash. + pH = REAGENT_NORMAL_PH if(!safety)//So it does not handle reactions when it need not to handle_reactions() if(my_atom) diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index d1c18f0510..22aadc06f4 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -24,9 +24,11 @@ circuit = /obj/item/circuitboard/machine/chem_dispenser var/obj/item/stock_parts/cell/cell var/powerefficiency = 0.0666666 + var/dispenceUnit = 5 var/amount = 30 var/recharge_amount = 10 var/recharge_counter = 0 + var/canStore = TRUE//If this can hold reagents or not var/mutable_appearance/beaker_overlay var/working_state = "dispenser_working" var/nopower_state = "dispenser_nopower" @@ -102,6 +104,7 @@ if(upgrade_reagents3) upgrade_reagents3 = sortList(upgrade_reagents3, /proc/cmp_reagents_asc) dispensable_reagents = sortList(dispensable_reagents, /proc/cmp_reagents_asc) + create_reagents(200, NO_REACT) update_icon() /obj/machinery/chem_dispenser/Destroy() @@ -190,24 +193,27 @@ data["amount"] = amount data["energy"] = cell.charge ? cell.charge * powerefficiency : "0" //To prevent NaN in the UI. data["maxEnergy"] = cell.maxcharge * powerefficiency + data["storedVol"] = reagents.total_volume + data["maxVol"] = reagents.maximum_volume data["isBeakerLoaded"] = beaker ? 1 : 0 + data["stepAmount"] = dispenceUnit + data["canStore"] = canStore var/beakerContents[0] var/beakerCurrentVolume = 0 if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "id" = R.type, "volume" = round(R.volume, 0.01)))) // list in a list because Byond merges the first list... beakerCurrentVolume += R.volume data["beakerContents"] = beakerContents if (beaker) - data["beakerCurrentVolume"] = beakerCurrentVolume + data["beakerCurrentVolume"] = round(beakerCurrentVolume, 0.01) data["beakerMaxVolume"] = beaker.volume data["beakerTransferAmounts"] = beaker.possible_transfer_amounts - data["beakerCurrentpH"] = beaker.reagents.pH //pH accuracy for(var/obj/item/stock_parts/capacitor/C in component_parts) - data["partRating"]= 10**(C.rating-1) + data["beakerCurrentpH"] = round(beaker.reagents.pH, 10**-(C.rating+1)) else data["beakerCurrentVolume"] = null @@ -225,11 +231,17 @@ var/chemname = temp.name if(is_hallucinating && prob(5)) chemname = "[pick_list_replacements("hallucination.json", "chemicals")]" - chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name)))) + chemicals.Add(list(list("title" = chemname, "id" = ckey(temp.name), "pH" = temp.pH, "pHCol" = ConvertpHToCol(temp.pH)))) data["chemicals"] = chemicals data["recipes"] = saved_recipes data["recordingRecipe"] = recording_recipe + + var/storedContents[0] + if(reagents.total_volume) + for(var/datum/reagent/N in reagents.reagent_list) + storedContents.Add(list(list("name" = N.name, "id" = N.type, "volume" = N.volume))) + data["storedContents"] = storedContents return data /obj/machinery/chem_dispenser/ui_act(action, params) @@ -240,10 +252,9 @@ if(!is_operational() || QDELETED(beaker)) return var/target = text2num(params["target"]) - if(target in beaker.possible_transfer_amounts) - amount = target - work_animation() - . = TRUE + SetAmount(target) + work_animation() + . = TRUE if("dispense") if(!is_operational() || QDELETED(cell)) return @@ -269,10 +280,9 @@ if(!is_operational() || recording_recipe) return var/amount = text2num(params["amount"]) - if(beaker && (amount in beaker.possible_transfer_amounts)) - beaker.reagents.remove_all(amount) - work_animation() - . = TRUE + beaker.reagents.remove_all(amount) //This should be set correctly in "amount" + work_animation() + . = TRUE if("eject") replace_beaker(usr) . = TRUE @@ -350,6 +360,52 @@ recording_recipe = null . = TRUE + //Storing and unstoring reagents + if("store") + if(!is_operational() || QDELETED(cell)) + return + if(!beaker) + return + if(recording_recipe) + say("Cannot store while recording!") + return + if(beaker.reagents.fermiIsReacting) + say("Cannot store ongoing reactions!") + return + var/reagent = text2path(params["id"]) + var/datum/reagent/R = beaker.reagents.has_reagent(reagent) + var/potentialAmount = min(amount, R.volume) + if(reagents.total_volume+potentialAmount > reagents.maximum_volume) + say("Not enough storage space left!") + return + beaker.reagents.trans_id_to(src, R.type, potentialAmount) + work_animation() + . = TRUE + + if("unstore") + if(!is_operational() || QDELETED(cell)) + return + if(!beaker) + return + if(recording_recipe) + say("Cannot distribute while recording!") + return + var/reagent = text2path(params["id"]) + var/datum/reagent/R = reagents.has_reagent(reagent) + reagents.trans_id_to(beaker, R.type, amount) + work_animation() + . = TRUE + +/obj/machinery/chem_dispenser/proc/SetAmount(inputAmount) + if(inputAmount % 5 == 0) //Always allow 5u values + amount = inputAmount + return + inputAmount -= inputAmount % dispenceUnit + if(inputAmount == 0) //Prevent ghost entries in macros + amount = dispenceUnit + return + amount = inputAmount + /obj/machinery/chem_dispenser/attackby(obj/item/I, mob/user, params) if(default_unfasten_wrench(user, I)) return @@ -402,6 +458,8 @@ cell = P for(var/obj/item/stock_parts/matter_bin/M in component_parts) newpowereff += 0.0166666666*M.rating + if(reagents) + reagents.maximum_volume = 200*(M.rating) for(var/obj/item/stock_parts/capacitor/C in component_parts) recharge_amount *= C.rating for(var/obj/item/stock_parts/manipulator/M in component_parts) @@ -411,6 +469,15 @@ dispensable_reagents |= upgrade_reagents2 if(M.rating > 3) dispensable_reagents |= upgrade_reagents3 + switch(M.rating) + if(-INFINITY to 1) + dispenceUnit = 5 + if(2) + dispenceUnit = 3 + if(3) + dispenceUnit = 2 + if(4 to INFINITY) + dispenceUnit = 1 powerefficiency = round(newpowereff, 0.01) /obj/machinery/chem_dispenser/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) @@ -421,6 +488,8 @@ user.put_in_hands(B) if(new_beaker) beaker = new_beaker + if(amount > beaker.reagents.maximum_volume) + amount = beaker.reagents.maximum_volume else beaker = null update_icon() @@ -439,6 +508,32 @@ replace_beaker(user) return TRUE +/obj/machinery/chem_dispenser/proc/ConvertpHToCol(pH) + switch(pH) + if(-INFINITY to 1) + return "red" + if(1 to 2) + return "orange" + if(2 to 3) + return "average" + if(3 to 4) + return "yellow" + if(4 to 5) + return "olive" + if(5 to 6) + return "good" + if(6 to 8) + return "green" + if(8 to 9.5) + return "teal" + if(9.5 to 11) + return "blue" + if(11 to 12.5) + return "violet" + if(12.5 to INFINITY) + return "purple" + + /obj/machinery/chem_dispenser/drinks/Initialize() . = ..() AddComponent(/datum/component/simple_rotation, ROTATION_ALTCLICK | ROTATION_CLOCKWISE) @@ -466,6 +561,7 @@ b_o.pixel_x = rand(-9, 9) return b_o + /obj/machinery/chem_dispenser/drinks name = "soda dispenser" desc = "Contains a large reservoir of soft drinks." @@ -479,6 +575,7 @@ working_state = null nopower_state = null pass_flags = PASSTABLE + canStore = FALSE dispensable_reagents = list( /datum/reagent/water, /datum/reagent/consumable/ice, @@ -611,12 +708,14 @@ dispensable_reagents = list(/datum/reagent/toxin/mutagen) upgrade_reagents = null emagged_reagents = list(/datum/reagent/toxin/plasma) + canStore = FALSE /obj/machinery/chem_dispenser/mutagensaltpeter name = "botanical chemical dispenser" desc = "Creates and dispenses chemicals useful for botany." flags_1 = NODECONSTRUCT_1 + canStore = FALSE dispensable_reagents = list( /datum/reagent/toxin/mutagen, @@ -739,6 +838,7 @@ working_state = "minidispenser_working" nopower_state = "minidispenser_nopower" circuit = /obj/item/circuitboard/machine/chem_dispenser/apothecary + canStore = FALSE powerefficiency = 0.0833333 dispensable_reagents = list( //radium and stable plasma moved to upgrade tier 1 and 2, they've little to do with most medicines anyway. /datum/reagent/hydrogen, diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index 8c3b9c7f99..cd8ee2d986 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -126,7 +126,7 @@ var beakerContents[0] if(beaker) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "volume" = R.volume, "purity" = R.purity))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "volume" = round(R.volume, 0.01), "purity" = round(R.purity, 0.01)))) // list in a list because Byond merges the first list... data["beakerContents"] = beakerContents return data diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 89fdb52b2a..285ef70433 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -184,13 +184,13 @@ var/beakerContents[0] if(beaker) for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents.Add(list(list("name" = R.name, "id" = ckey(R.name), "volume" = R.volume))) // list in a list because Byond merges the first list... + beakerContents.Add(list(list("name" = R.name, "id" = R.type, "volume" = R.volume))) // list in a list because Byond merges the first list... data["beakerContents"] = beakerContents var/bufferContents[0] if(reagents.total_volume) for(var/datum/reagent/N in reagents.reagent_list) - bufferContents.Add(list(list("name" = N.name, "id" = ckey(N.name), "volume" = N.volume))) // ^ + bufferContents.Add(list(list("name" = N.name, "id" = N.type, "volume" = N.volume))) // ^ data["bufferContents"] = bufferContents //Calculated at init time as it never changes @@ -216,7 +216,7 @@ if(action == "transfer") if(!beaker) return FALSE - var/reagent = GLOB.name2reagent[params["id"]] + var/reagent = text2path(params["id"]) var/amount = text2num(params["amount"]) var/to_container = params["to"] // Custom amount @@ -386,7 +386,7 @@ if(action == "analyze") // var/datum/reagent/R = GLOB.name2reagent[params["id"]] - var/reagent = GLOB.name2reagent[params["id"]] + var/reagent = text2path(params["id"]) var/datum/reagent/R = GLOB.chemical_reagents_list[reagent] if(R) var/state = "Unknown" @@ -405,7 +405,7 @@ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity, "inverseRatioF" = initial(R.inverse_chem_val), "purityE" = initial(Rcr.PurityMin), "minTemp" = initial(Rcr.OptimalTempMin), "maxTemp" = initial(Rcr.OptimalTempMax), "eTemp" = initial(Rcr.ExplodeTemp), "pHpeak" = pHpeakCache) else fermianalyze = FALSE - analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold)) + analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = R.purity) screen = "analyze" return TRUE diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index d4d66af240..f2e9bd9e1a 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -116,7 +116,10 @@ holder.remove_reagent(id, added_volume*temp_ratio) if(St.purity < 1) St.volume *= St.purity + added_volume *= St.purity St.purity = 1 + if(!N) + return var/amount = clamp(0.002, 0, N.volume) N.volume -= amount St.data["grown_volume"] = St.data["grown_volume"] + added_volume diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm index 2d4d363efb..c6fae2ff8f 100644 --- a/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm +++ b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm @@ -334,39 +334,75 @@ holder.clear_reagents() /datum/reagent/fermi/acidic_buffer - name = "Acidic buffer" + name = "Strong acidic buffer" description = "This reagent will consume itself and move the pH of a beaker towards acidity when added to another." color = "#fbc314" pH = 0 + chemical_flags = REAGENT_FORCEONNEW can_synth = TRUE + var/strength = 1.5 //Consumes self on addition and shifts pH /datum/reagent/fermi/acidic_buffer/on_new(datapH) + if(!holder) + return ..() + if(holder.reagents_holder_flags & NO_REACT) + return..() if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return ..() data = datapH if(LAZYLEN(holder.reagent_list) == 1) return ..() - holder.pH = ((holder.pH * holder.total_volume)+(pH * (volume)))/(holder.total_volume + (volume)) + if(holder.pH < pH) + holder.my_atom.visible_message("The beaker fizzes as the buffer is added, to no effect.") + playsound(holder.my_atom, 'sound/FermiChem/bufferadd.ogg', 50, 1) + return ..() + holder.pH = clamp((((holder.pH * (holder.total_volume-(volume*strength)))+(pH * (volume*strength)) )/holder.total_volume), 0, 14) //This is BEFORE removal holder.my_atom.visible_message("The beaker fizzes as the pH changes!") playsound(holder.my_atom, 'sound/FermiChem/bufferadd.ogg', 50, 1) holder.remove_reagent(type, volume, ignore_pH = TRUE) ..() +/datum/reagent/fermi/acidic_buffer/weak + name = "Acidic buffer" + description = "This reagent will consume itself and move the pH of a beaker towards acidity when added to another." + color = "#fbf344" + pH = 4 + can_synth = TRUE + strength = 0.25 + /datum/reagent/fermi/basic_buffer - name = "Basic buffer" + name = "Strong basic buffer" description = "This reagent will consume itself and move the pH of a beaker towards alkalinity when added to another." color = "#3853a4" pH = 14 + chemical_flags = REAGENT_FORCEONNEW can_synth = TRUE + var/strength = 1.5 + +/datum/reagent/fermi/basic_buffer/weak + name = "Basic buffer" + description = "This reagent will consume itself and move the pH of a beaker towards alkalinity when added to another." + color = "#5873c4" + pH = 10 + can_synth = TRUE + strength = 0.25 /datum/reagent/fermi/basic_buffer/on_new(datapH) + if(!holder) + return ..() + if(holder.reagents_holder_flags & NO_REACT) + return..() if(holder.has_reagent(/datum/reagent/stabilizing_agent)) return ..() data = datapH if(LAZYLEN(holder.reagent_list) == 1) return ..() - holder.pH = ((holder.pH * holder.total_volume)+(pH * (volume)))/(holder.total_volume + (volume)) + if(holder.pH > pH) + holder.my_atom.visible_message("The beaker froths as the buffer is added, to no effect.") + playsound(holder.my_atom, 'sound/FermiChem/bufferadd.ogg', 50, 1) + return ..() + holder.pH = clamp((((holder.pH * (holder.total_volume-(volume*strength)))+(pH * (volume*strength)) )/holder.total_volume), 0, 14) //This is BEFORE removal holder.my_atom.visible_message("The beaker froths as the pH changes!") playsound(holder.my_atom, 'sound/FermiChem/bufferadd.ogg', 50, 1) holder.remove_reagent(type, volume, ignore_pH = TRUE) diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.js b/tgui/packages/tgui/interfaces/ChemDispenser.js index 0724a9e85f..23498caf34 100644 --- a/tgui/packages/tgui/interfaces/ChemDispenser.js +++ b/tgui/packages/tgui/interfaces/ChemDispenser.js @@ -1,13 +1,20 @@ import { toFixed } from 'common/math'; import { toTitleCase } from 'common/string'; import { Fragment } from 'inferno'; -import { useBackend } from '../backend'; -import { AnimatedNumber, Box, Button, Icon, LabeledList, ProgressBar, Section } from '../components'; +import { useBackend, useLocalState } from '../backend'; +import { AnimatedNumber, Box, Button, Icon, LabeledList, ProgressBar, Section, Table, NumberInput } from '../components'; import { Window } from '../layouts'; export const ChemDispenser = (props, context) => { const { act, data } = useBackend(context); const recording = !!data.recordingRecipe; + const [hasCol, setHasCol] = useLocalState( + context, 'fs_title', false); + const [modeToggle, setModeToggle] = useLocalState( + context, 'mode_toggle', true); + const { + storedContents = [], + } = data; // TODO: Change how this piece of shit is built on server side // It has to be a list, not a fucking OBJECT! const recipes = Object.keys(data.recipes) @@ -28,17 +35,27 @@ export const ChemDispenser = (props, context) => { return (
- - Recording - - )}> + buttons={ + [(recording && ( + + + Recording + + )), +
( -
+ {!!data.canStore && ( +
+ Transfer amount: + + u + + }> + + {toFixed(data.storedVol) + ' units / ' + data.maxVol + ' units'} + + + {storedContents.map(chemical => ( + + ))} + +
+ )}
{ {recording && 'Virtual beaker' || data.isBeakerLoaded - && ( - - - /{data.beakerMaxVolume} units - - ) + && ( + + + /{data.beakerMaxVolume} units + + ) || 'No beaker'} { {(!data.isBeakerLoaded && !recording) && 'N/A' || beakerContents.length === 0 && 'Nothing'} - {beakerContents.map(chemical => ( - - - {' '} - units of {chemical.name} - - ))} + + {beakerContents.map(chemical => ( + + ))} + pH: @@ -196,3 +260,60 @@ export const ChemDispenser = (props, context) => { ); }; + + +const ChemicalBuffer = Table; + +const ChemicalBufferEntry = (props, context) => { + const { act, data } = useBackend(context); + const { chemical, transferTo } = props; + return ( + + + + {` units of ${chemical.name}`} + + +