Merge pull request #13966 from Thalpy/Dispencer-UI-change

[Ready] Fixes and tweaks for the chemistry machine suite
This commit is contained in:
silicons
2021-02-06 11:41:08 -07:00
committed by GitHub
9 changed files with 329 additions and 69 deletions
+3 -3
View File
@@ -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)
@@ -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,
@@ -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
@@ -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
@@ -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
@@ -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("<span class='warning'>The beaker fizzes as the buffer is added, to no effect.</b></span>")
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("<span class='warning'>The beaker fizzes as the pH changes!</b></span>")
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("<span class='warning'>The beaker froths as the buffer is added, to no effect.</b></span>")
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("<span class='warning'>The beaker froths as the pH changes!</b></span>")
playsound(holder.my_atom, 'sound/FermiChem/bufferadd.ogg', 50, 1)
holder.remove_reagent(type, volume, ignore_pH = TRUE)
+159 -38
View File
@@ -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 (
<Window
width={565}
height={620}
height={data.canStore ? 720 : 620}
resizable>
<Window.Content scrollable>
<Section
title="Status"
buttons={recording && (
<Box inline mx={1} color="red">
<Icon name="circle" mr={1} />
Recording
</Box>
)}>
buttons={
[(recording && (
<Box inline mx={1} color="red">
<Icon name="circle" mr={1} />
Recording
</Box>
)),
<Button // eslint-disable-line
key="colorButton"
icon="cog"
disabled={!data.isBeakerLoaded}
tooltip="Alternate between buttons and radial input"
tooltipPosition="bottom-left"
selected={modeToggle}
onClick={() => setModeToggle(!modeToggle)} />]
}>
<LabeledList>
<LabeledList.Item label="Energy">
<ProgressBar
@@ -103,18 +120,40 @@ export const ChemDispenser = (props, context) => {
</Box>
</Section>
<Section
key="dispense"
title="Dispense"
buttons={(
beakerTransferAmounts.map(amount => (
<Button
key={amount}
icon="plus"
selected={amount === data.amount}
content={amount}
onClick={() => act('amount', {
target: amount,
})} />
))
[modeToggle ? (
beakerTransferAmounts.map(amount => (
<Button
key={amount}
icon="plus"
selected={amount === data.amount}
content={amount}
onClick={() => act('amount', {
target: amount,
})} />
))) : (!!data.isBeakerLoaded
&& <NumberInput
key="dispenseInput"
width="65px"
unit="u"
step={data.stepAmount}
stepPixelSize={data.stepAmount}
disabled={!data.isBeakerLoaded}
value={data.amount}
minValue={1}
maxValue={data.beakerMaxVolume}
onDrag={(e, amount) => act('amount', {
target: amount,
})} />),
<Button // eslint-disable-line
key="colorButton"
icon="cog"
tooltip="Color code the reagents by pH"
tooltipPosition="bottom-left"
selected={hasCol}
onClick={() => setHasCol(!hasCol)} />]
)}>
<Box mr={-1}>
{data.chemicals.map(chemical => (
@@ -124,12 +163,40 @@ export const ChemDispenser = (props, context) => {
width="129.5px"
lineHeight={1.75}
content={chemical.title}
tooltip={"pH: " + chemical.pH}
backgroundColor={hasCol ? chemical.pHCol : "primary"}
onClick={() => act('dispense', {
reagent: chemical.id,
})} />
))}
</Box>
</Section>
{!!data.canStore && (
<Section
title="Storage"
buttons={
<Box>
Transfer amount:
<AnimatedNumber
initial={5}
value={data.amount} />
u
</Box>
}>
<ProgressBar
value={data.storedVol / data.maxVol}>
{toFixed(data.storedVol) + ' units / ' + data.maxVol + ' units'}
</ProgressBar>
<ChemicalBuffer>
{storedContents.map(chemical => (
<ChemicalBufferEntry
key={chemical.id}
chemical={chemical}
transferTo="beaker" />
))}
</ChemicalBuffer>
</Section>
)}
<Section
title="Beaker"
buttons={(
@@ -155,14 +222,14 @@ export const ChemDispenser = (props, context) => {
{recording
&& 'Virtual beaker'
|| data.isBeakerLoaded
&& (
<Fragment>
<AnimatedNumber
initial={0}
value={data.beakerCurrentVolume} />
/{data.beakerMaxVolume} units
</Fragment>
)
&& (
<Fragment>
<AnimatedNumber
initial={0}
value={data.beakerCurrentVolume} />
/{data.beakerMaxVolume} units
</Fragment>
)
|| 'No beaker'}
</LabeledList.Item>
<LabeledList.Item
@@ -171,17 +238,14 @@ export const ChemDispenser = (props, context) => {
{(!data.isBeakerLoaded && !recording) && 'N/A'
|| beakerContents.length === 0 && 'Nothing'}
</Box>
{beakerContents.map(chemical => (
<Box
key={chemical.name}
color="label">
<AnimatedNumber
initial={0}
value={chemical.volume} />
{' '}
units of {chemical.name}
</Box>
))}
<ChemicalBeaker>
{beakerContents.map(chemical => (
<ChemicalBeakerEntry
key={chemical.id}
chemical={chemical}
transferTo="beaker" />
))}
</ChemicalBeaker>
<Box
key={"pH"}>
pH:
@@ -196,3 +260,60 @@ export const ChemDispenser = (props, context) => {
</Window>
);
};
const ChemicalBuffer = Table;
const ChemicalBufferEntry = (props, context) => {
const { act, data } = useBackend(context);
const { chemical, transferTo } = props;
return (
<Table.Row key={chemical.id}>
<Table.Cell color="label">
<AnimatedNumber
value={chemical.volume}
initial={0} />
{` units of ${chemical.name}`}
</Table.Cell>
<Table.Cell collapsing>
<Button
content="Dispense"
icon="download"
disabled={!!data.recordingRecipe || !data.isBeakerLoaded}
mt={0.5}
onClick={() => act('unstore', {
id: chemical.id,
})} />
</Table.Cell>
</Table.Row>
);
};
const ChemicalBeaker = Table;
const ChemicalBeakerEntry = (props, context) => {
const { act, data } = useBackend(context);
const { chemical, transferTo } = props;
return (
<Table.Row key={chemical.id}>
<Table.Cell color="label">
<AnimatedNumber
value={chemical.volume}
initial={0} />
{` units of ${chemical.name}`}
</Table.Cell>
{!!data.canStore && (
<Table.Cell collapsing>
<Button
content="Store"
icon="upload"
disabled={!!data.recordingRecipe}
mt={0.5}
onClick={() => act('store', {
id: chemical.id,
})} />
</Table.Cell>
)}
</Table.Row>
);
};
+4 -4
View File
@@ -400,11 +400,11 @@ const AnalysisResults = (props, context) => {
<LabeledList.Item label="Addiction Threshold">
{analyzeVars.addicD}
</LabeledList.Item>
{!!fermianalyze && ( // why did you do that before? it's fucking bad.
<LabeledList.Item label="Purity">
{analyzeVars.purityF}
</LabeledList.Item>
{!! data.fermianalyze && ( // why did you do that before? it's bad.
<Fragment>
<LabeledList.Item label="Purity">
{analyzeVars.purityF}
</LabeledList.Item>
<LabeledList.Item label="Inverse Ratio">
{analyzeVars.inverseRatioF}
</LabeledList.Item>
File diff suppressed because one or more lines are too long