mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-19 11:08:35 +01:00
Plumbing chemical chamber UI and operation improvements (#78619)
## About The Pull Request 1. Fixes #58119 The word "Fixes" actually means the reaction chamber does it's job but "**does not guarantee**" the ph & temperature of the final solution is exactly within the ranges you specify especially for instantaneous reactions. There were 2 problems with the reaction chamber. a) **The ph chemistry of the reaction chamber was completely flipped upside down.** Let's look at the code https://github.com/tgstation/tgstation/blob/cfe13015203480dafaf3fce1589ad6045e708442/code/modules/plumbing/plumbers/reaction_chamber.dm#L152-L155 This was adding basic buffer when the solution was getting to basic & acid buffer when the solution was getting too acidic The correct mode of operation is as follows - If the ph is getting way too **acidic** i.e. it's ph **decreases** and becomes **lower** than the acidic limit then we need to add some **base buffer** so as to **increase** it's value and bring it back **above** the acidic limit - If the ph is getting way to **basic** i.e. it's ph **increases** and becomes **higher** than the alkaline limit then we need to add some **acidic buffer** so as to **decrease** it's value and bring it back **below** the alkaline limit b) **The reaction chamber did it's work only half of the time** Looking at the code https://github.com/tgstation/tgstation/blob/cfe13015203480dafaf3fce1589ad6045e708442/code/modules/plumbing/plumbers/reaction_chamber.dm#L152 The reaction chamber would only balance the ph of the solution **ONLY** when an reaction was taking place i.e. only when `reagents.is_reacting` is true. It would not attempt to balance the ph of the reagents as and when they were coming in i.e when `emptying` is FALSE. This means if an reaction took only like 0.5 to 1 second to occur the ph would be balanced only during that very short interval of time and so it would not make much of difference. So the 2 problems were the chamber was doing its job wrong and would do it only half the time. Again re-emphasizing even with these patches the chamber "**does not guarantee**" the final solution ph will be within those ranges. This is because the nature of reactions is unpredictable. Some reactions can take place so fast and have huge drastic ph changes that the reaction chamber would be ineffective regardless of how much base/acid it used to balance the reaction. The only thing is that it does it's job correctly and too the best of its ability with no guarentees. 3. The plumbing mixing chamber(parent type of the plumbing chemical reaction chamber) which you make via research plumbing RCD, UI now also uses an TGUI input list to accept chemicals so no more manual typing. 4. Both these types have their UI files converted Typescript cause why not ## Changelog 🆑 fix: plumbing reaction chamber now balances the ph of it's solution correctly to the best of it's ability so no guarantees code: converted plumbing reaction chamber & mixing chamber UI files to Typescript refactor: plumbing mixing chamber now also accepts an TGUI input list to input it's chemicals /🆑 --------- Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
///a reaction chamber for plumbing. pretty much everything can react, but this one keeps the reagents separated and only reacts under your given terms
|
||||
|
||||
/// coefficient to convert temperature to joules. same lvl as acclimator
|
||||
#define HEATER_COEFFICIENT 0.05
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber
|
||||
name = "mixing chamber"
|
||||
desc = "Keeps chemicals separated until given conditions are met."
|
||||
@@ -19,9 +22,6 @@
|
||||
|
||||
///towards which temperature do we build (except during draining)?
|
||||
var/target_temperature = 300
|
||||
///cool/heat power
|
||||
var/heater_coefficient = 0.05 //same lvl as acclimator
|
||||
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/Initialize(mapload, bolt, layer)
|
||||
. = ..()
|
||||
@@ -35,30 +35,48 @@
|
||||
/// Handles properly detaching signal hooks.
|
||||
/obj/machinery/plumbing/reaction_chamber/proc/on_reagents_del(datum/reagents/reagents)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
UnregisterSignal(reagents, list(COMSIG_REAGENTS_REM_REAGENT, COMSIG_REAGENTS_DEL_REAGENT, COMSIG_REAGENTS_CLEAR_REAGENTS, COMSIG_REAGENTS_REACTED, COMSIG_QDELETING))
|
||||
return NONE
|
||||
|
||||
/// Handles stopping the emptying process when the chamber empties.
|
||||
/obj/machinery/plumbing/reaction_chamber/proc/on_reagent_change(datum/reagents/holder, ...)
|
||||
SIGNAL_HANDLER
|
||||
if(holder.total_volume == 0 && emptying) //we were emptying, but now we aren't
|
||||
|
||||
if(!holder.total_volume && emptying) //we were emptying, but now we aren't
|
||||
emptying = FALSE
|
||||
holder.flags |= NO_REACT
|
||||
return NONE
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/process(seconds_per_tick)
|
||||
if(!emptying || reagents.is_reacting) //suspend heating/cooling during emptying phase
|
||||
reagents.adjust_thermal_energy((target_temperature - reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
|
||||
//half the power for getting reagents in
|
||||
var/power_usage = active_power_usage * 0.5
|
||||
|
||||
if(!emptying || reagents.is_reacting)
|
||||
//do reactions and stuff
|
||||
reagents.handle_reactions()
|
||||
|
||||
use_power(active_power_usage * seconds_per_tick)
|
||||
//adjust temperature of final solution
|
||||
var/temp_diff = target_temperature - reagents.chem_temp
|
||||
if(abs(temp_diff) > 0.01) //if we are not close enough keep going
|
||||
reagents.adjust_thermal_energy(temp_diff * HEATER_COEFFICIENT * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.total_volume) //keep constant with chem heater
|
||||
|
||||
//do other stuff with final solution
|
||||
handle_reagents(seconds_per_tick)
|
||||
|
||||
//full power for doing reactions
|
||||
power_usage *= 2
|
||||
|
||||
use_power(power_usage * seconds_per_tick)
|
||||
|
||||
///For subtypes that want to do additional reagent handling
|
||||
/obj/machinery/plumbing/reaction_chamber/proc/handle_reagents(seconds_per_tick)
|
||||
return
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/power_change()
|
||||
. = ..()
|
||||
if(use_power != NO_POWER_USE)
|
||||
icon_state = initial(icon_state) + "_on"
|
||||
else
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
icon_state = initial(icon_state) + "[use_power != NO_POWER_USE ? "_on" : ""]"
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
@@ -67,31 +85,29 @@
|
||||
ui.open()
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
. = list()
|
||||
|
||||
var/list/reagents_data = list()
|
||||
for(var/datum/reagent/required_reagent as anything in required_reagents) //make a list where the key is text, because that looks alot better in the ui than a typepath
|
||||
var/list/reagent_data = list()
|
||||
reagent_data["name"] = initial(required_reagent.name)
|
||||
reagent_data["required_reagent"] = required_reagents[required_reagent]
|
||||
reagent_data["volume"] = required_reagents[required_reagent]
|
||||
reagents_data += list(reagent_data)
|
||||
|
||||
data["reagents"] = reagents_data
|
||||
data["emptying"] = emptying
|
||||
data["temperature"] = round(reagents.chem_temp, 0.1)
|
||||
data["targetTemp"] = target_temperature
|
||||
data["isReacting"] = reagents.is_reacting
|
||||
return data
|
||||
.["reagents"] = reagents_data
|
||||
.["emptying"] = emptying
|
||||
.["temperature"] = round(reagents.chem_temp, 0.1)
|
||||
.["targetTemp"] = target_temperature
|
||||
.["isReacting"] = reagents.is_reacting
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/ui_act(action, params)
|
||||
/obj/machinery/plumbing/reaction_chamber/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
|
||||
. = FALSE
|
||||
switch(action)
|
||||
if("add")
|
||||
var/selected_reagent = tgui_input_list(usr, "Select reagent", "Reagent", GLOB.chemical_name_list)
|
||||
var/selected_reagent = tgui_input_list(ui.user, "Select reagent", "Reagent", GLOB.chemical_name_list)
|
||||
if(!selected_reagent)
|
||||
return TRUE
|
||||
|
||||
@@ -104,32 +120,41 @@
|
||||
if(input_amount)
|
||||
required_reagents[input_reagent] = input_amount
|
||||
|
||||
. = TRUE
|
||||
return TRUE
|
||||
|
||||
if("remove")
|
||||
var/reagent = get_chem_id(params["chem"])
|
||||
if(reagent)
|
||||
required_reagents.Remove(reagent)
|
||||
. = TRUE
|
||||
return TRUE
|
||||
|
||||
if("temperature")
|
||||
var/target = text2num(params["target"])
|
||||
if(target != null)
|
||||
target_temperature=clamp(target, 0, 1000)
|
||||
.=TRUE
|
||||
target_temperature = clamp(target, 0, 1000)
|
||||
return TRUE
|
||||
|
||||
var/result = handle_ui_act(action, params, ui, state)
|
||||
if(isnull(result))
|
||||
result = FALSE
|
||||
return result
|
||||
|
||||
/// For custom handling of ui actions from inside a subtype
|
||||
/obj/machinery/plumbing/reaction_chamber/proc/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
|
||||
return null
|
||||
|
||||
///Chemistry version of reaction chamber that allows for acid and base buffers to be used while reacting
|
||||
/obj/machinery/plumbing/reaction_chamber/chem
|
||||
name = "reaction chamber"
|
||||
|
||||
///If above this pH, we start dumping buffer into it
|
||||
var/acidic_limit = 9
|
||||
///If below this pH, we start dumping buffer into it
|
||||
var/alkaline_limit = 5
|
||||
var/acidic_limit = 5
|
||||
///If above this pH, we start dumping acid into it
|
||||
var/alkaline_limit = 9
|
||||
|
||||
///Beaker that holds the acidic buffer. I don't want to deal with snowflaking so it's just a separate thing. It's a small (50u) beaker
|
||||
///beaker that holds the acidic buffer(50u)
|
||||
var/obj/item/reagent_containers/cup/beaker/acidic_beaker
|
||||
///beaker that holds the alkaline buffer.
|
||||
///beaker that holds the alkaline buffer(50u).
|
||||
var/obj/item/reagent_containers/cup/beaker/alkaline_beaker
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/chem/Initialize(mapload, bolt, layer)
|
||||
@@ -147,13 +172,27 @@
|
||||
QDEL_NULL(alkaline_beaker)
|
||||
return ..()
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/chem/process(seconds_per_tick)
|
||||
//add acidic/alkaine buffer if over/under limit
|
||||
if(reagents.is_reacting && reagents.ph < alkaline_limit)
|
||||
alkaline_beaker.reagents.trans_to(reagents, 1 * seconds_per_tick)
|
||||
if(reagents.is_reacting && reagents.ph > acidic_limit)
|
||||
acidic_beaker.reagents.trans_to(reagents, 1 * seconds_per_tick)
|
||||
..()
|
||||
/obj/machinery/plumbing/reaction_chamber/chem/handle_reagents(seconds_per_tick)
|
||||
while(reagents.ph < acidic_limit || reagents.ph > alkaline_limit)
|
||||
if(machine_stat & NOPOWER)
|
||||
return
|
||||
|
||||
/**
|
||||
* figure out which buffer to transfer to restore balance
|
||||
* if solution is getting too basic(high ph) add some acid to lower it's value
|
||||
* else if solution is getting too acidic(low ph) add some base to increase it's value
|
||||
*/
|
||||
var/datum/reagents/buffer = reagents.ph > alkaline_limit ? acidic_beaker.reagents : alkaline_beaker.reagents
|
||||
if(!buffer.total_volume)
|
||||
return
|
||||
|
||||
//transfer buffer and handle reactions, not a proven math but looks logical
|
||||
var/transfer_amount = FLOOR((reagents.ph > alkaline_limit ? (reagents.ph - alkaline_limit) : (acidic_limit - reagents.ph)) * seconds_per_tick, CHEMICAL_QUANTISATION_LEVEL)
|
||||
if(transfer_amount <= CHEMICAL_QUANTISATION_LEVEL || !buffer.trans_to(reagents, transfer_amount))
|
||||
return
|
||||
|
||||
//some power for accurate ph balancing
|
||||
use_power(active_power_usage * 0.2 * seconds_per_tick)
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/chem/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
@@ -167,16 +206,16 @@
|
||||
.["reagentAcidic"] = acidic_limit
|
||||
.["reagentAlkaline"] = alkaline_limit
|
||||
|
||||
/obj/machinery/plumbing/reaction_chamber/chem/ui_act(action, params)
|
||||
. = ..()
|
||||
if (.)
|
||||
return
|
||||
/obj/machinery/plumbing/reaction_chamber/chem/handle_ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = TRUE
|
||||
|
||||
switch(action)
|
||||
if("acidic")
|
||||
acidic_limit = round(text2num(params["target"]))
|
||||
acidic_limit = clamp(round(text2num(params["target"])), 0, alkaline_limit)
|
||||
if("alkaline")
|
||||
alkaline_limit = round(text2num(params["target"]))
|
||||
alkaline_limit = clamp(round(text2num(params["target"])), acidic_limit + 0.01, 14)
|
||||
else
|
||||
return FALSE
|
||||
|
||||
return TRUE
|
||||
|
||||
#undef HEATER_COEFFICIENT
|
||||
|
||||
+29
-22
@@ -1,10 +1,24 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Input, NumberInput, Section, Stack } from '../components';
|
||||
import { AnimatedNumber, Box, Button, NumberInput, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { round, toFixed } from 'common/math';
|
||||
import { BooleanLike } from 'common/react';
|
||||
|
||||
type Reagent = {
|
||||
name: string;
|
||||
volume: number;
|
||||
};
|
||||
|
||||
export type MixingData = {
|
||||
reagents: Reagent[];
|
||||
emptying: BooleanLike;
|
||||
temperature: number;
|
||||
targetTemp: number;
|
||||
isReacting: BooleanLike;
|
||||
};
|
||||
|
||||
export const ChemMixingChamber = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { act, data } = useBackend<MixingData>(context);
|
||||
|
||||
const [reagentName, setReagentName] = useLocalState(
|
||||
context,
|
||||
@@ -17,7 +31,7 @@ export const ChemMixingChamber = (props, context) => {
|
||||
1
|
||||
);
|
||||
|
||||
const { emptying, temperature, ph, targetTemp, isReacting } = data;
|
||||
const { emptying, temperature, targetTemp, isReacting } = data;
|
||||
const reagents = data.reagents || [];
|
||||
return (
|
||||
<Window width={290} height={400}>
|
||||
@@ -35,7 +49,7 @@ export const ChemMixingChamber = (props, context) => {
|
||||
unit="K"
|
||||
step={10}
|
||||
stepPixelSize={3}
|
||||
value={round(targetTemp)}
|
||||
value={round(targetTemp, 0.1)}
|
||||
minValue={0}
|
||||
maxValue={1000}
|
||||
onDrag={(e, value) =>
|
||||
@@ -88,11 +102,15 @@ export const ChemMixingChamber = (props, context) => {
|
||||
<Stack.Item>
|
||||
<Stack fill>
|
||||
<Stack.Item grow>
|
||||
<Input
|
||||
fluid
|
||||
value=""
|
||||
placeholder="Reagent Name"
|
||||
onInput={(e, value) => setReagentName(value)}
|
||||
<Button
|
||||
content="Add Reagent"
|
||||
color="good"
|
||||
icon="plus"
|
||||
onClick={() =>
|
||||
act('add', {
|
||||
amount: reagentQuantity,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
@@ -107,29 +125,18 @@ export const ChemMixingChamber = (props, context) => {
|
||||
/>
|
||||
<Box inline mr={1} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="plus"
|
||||
onClick={() =>
|
||||
act('add', {
|
||||
chem: reagentName,
|
||||
amount: reagentQuantity,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack vertical>
|
||||
{reagents.map((reagent) => (
|
||||
<Stack.Item key={reagent}>
|
||||
<Stack.Item key={reagent.name}>
|
||||
<Stack fill>
|
||||
<Stack.Item mt={0.25} textColor="label">
|
||||
{reagent.name + ':'}
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={0.25} grow>
|
||||
{reagent.required_reagent}
|
||||
{reagent.volume}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
+16
-9
@@ -2,9 +2,16 @@ import { useBackend, useLocalState } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, LabeledList, NumberInput, Section, RoundGauge, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { round, toFixed } from 'common/math';
|
||||
import { MixingData } from './ChemMixingChamber';
|
||||
|
||||
type ReactingData = MixingData & {
|
||||
ph: number;
|
||||
reagentAcidic: number;
|
||||
reagentAlkaline: number;
|
||||
};
|
||||
|
||||
export const ChemReactionChamber = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { act, data } = useBackend<ReactingData>(context);
|
||||
|
||||
const [reagentQuantity, setReagentQuantity] = useLocalState(
|
||||
context,
|
||||
@@ -38,7 +45,7 @@ export const ChemReactionChamber = (props, context) => {
|
||||
unit="K"
|
||||
step={10}
|
||||
stepPixelSize={3}
|
||||
value={round(targetTemp)}
|
||||
value={round(targetTemp, 0.1)}
|
||||
minValue={0}
|
||||
maxValue={1000}
|
||||
onDrag={(e, value) =>
|
||||
@@ -96,7 +103,7 @@ export const ChemReactionChamber = (props, context) => {
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="question"
|
||||
tooltip={multiline`
|
||||
tooltip={`
|
||||
In chemistry, pH is a scale used to specify
|
||||
the acidity or basicity of an aqueous solution.
|
||||
Acidic solutions are measured to have lower
|
||||
@@ -138,8 +145,8 @@ export const ChemReactionChamber = (props, context) => {
|
||||
<LabeledList.Item label="Acidic pH limit">
|
||||
<NumberInput
|
||||
value={reagentAcidic}
|
||||
minValue={-1000}
|
||||
maxValue={1000}
|
||||
minValue={0}
|
||||
maxValue={14}
|
||||
step={1}
|
||||
stepPixelSize={3}
|
||||
width="39px"
|
||||
@@ -153,8 +160,8 @@ export const ChemReactionChamber = (props, context) => {
|
||||
<LabeledList.Item label="Alkaline pH limit">
|
||||
<NumberInput
|
||||
value={reagentAlkaline}
|
||||
minValue={-1000}
|
||||
maxValue={1000}
|
||||
minValue={0}
|
||||
maxValue={14}
|
||||
step={1}
|
||||
stepPixelSize={3}
|
||||
width="39px"
|
||||
@@ -199,13 +206,13 @@ export const ChemReactionChamber = (props, context) => {
|
||||
<Stack.Item>
|
||||
<Stack vertical>
|
||||
{reagents.map((reagent) => (
|
||||
<Stack.Item key={reagent}>
|
||||
<Stack.Item key={reagent.name}>
|
||||
<Stack fill>
|
||||
<Stack.Item mt={0.25} textColor="label">
|
||||
{reagent.name + ':'}
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={0.25} grow>
|
||||
{reagent.required_reagent}
|
||||
{reagent.volume}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
Reference in New Issue
Block a user