diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm
index a07fd289f07..e3208d8d461 100644
--- a/code/modules/reagents/chemistry/machinery/chem_heater.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm
@@ -25,15 +25,14 @@
create_reagents(200, NO_REACT)
register_context()
-/obj/machinery/chem_heater/on_deconstruction(disassembled)
- beaker?.forceMove(drop_location())
-
/obj/machinery/chem_heater/Destroy()
if(beaker)
UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP)
QDEL_NULL(beaker)
return ..()
+/obj/machinery/chem_heater/on_deconstruction(disassembled)
+ beaker?.forceMove(drop_location())
/obj/machinery/chem_heater/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
@@ -60,10 +59,74 @@
return NONE
+/obj/machinery/chem_heater/examine(mob/user)
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.")
+ if(!QDELETED(beaker))
+ . += span_notice("It has a beaker of [beaker.reagents.total_volume] units capacity.")
+ if(beaker.reagents.is_reacting)
+ . += span_notice("Its contents are currently reacting.")
+ else
+ . += span_warning("There is no beaker inserted.")
+ . += span_notice("Its heating is turned [on ? "On" : "Off"].")
+ . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.")
+ if(panel_open)
+ . += span_notice("Its panel is open and can now be [EXAMINE_HINT("pried")] apart.")
+ else
+ . += span_notice("Its panel can be [EXAMINE_HINT("pried")] open")
+
/obj/machinery/chem_heater/update_icon_state()
icon_state = "[base_icon_state][beaker ? 1 : 0]b"
return ..()
+/obj/machinery/chem_heater/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == beaker)
+ UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP)
+ beaker = null
+ update_appearance()
+
+/obj/machinery/chem_heater/RefreshParts()
+ . = ..()
+ heater_coefficient = 0.1
+ for(var/datum/stock_part/micro_laser/micro_laser in component_parts)
+ heater_coefficient *= micro_laser.tier
+
+
+/obj/machinery/chem_heater/item_interaction(mob/living/user, obj/item/held_item, list/modifiers, is_right_clicking)
+ if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
+ return ..()
+
+ if(QDELETED(beaker))
+ if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe))
+ var/obj/item/reagent_containers/injector = held_item
+ injector.afterattack(beaker, user, proximity_flag = TRUE)
+ return ITEM_INTERACT_SUCCESS
+
+ if(is_reagent_container(held_item) && held_item.is_open_container())
+ if(replace_beaker(user, held_item))
+ ui_interact(user)
+ balloon_alert(user, "beaker added")
+ return ITEM_INTERACT_SUCCESS
+
+ return ..()
+
+/obj/machinery/chem_heater/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/chem_heater/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(default_deconstruction_screwdriver(user, "mixer0b", "[base_icon_state][beaker ? 1 : 0]b", tool))
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/chem_heater/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(default_deconstruction_crowbar(tool))
+ return ITEM_INTERACT_SUCCESS
+
/obj/machinery/chem_heater/attack_hand_secondary(mob/user, list/modifiers)
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
@@ -73,13 +136,6 @@
replace_beaker(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
-/obj/machinery/chem_heater/Exited(atom/movable/gone, direction)
- . = ..()
- if(gone == beaker)
- UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP)
- beaker = null
- update_appearance()
-
/obj/machinery/chem_heater/attack_robot_secondary(mob/user, list/modifiers)
return attack_hand_secondary(user, modifiers)
@@ -109,12 +165,6 @@
return TRUE
-/obj/machinery/chem_heater/RefreshParts()
- . = ..()
- heater_coefficient = 0.1
- for(var/datum/stock_part/micro_laser/micro_laser in component_parts)
- heater_coefficient *= micro_laser.tier
-
/**
* Heats the reagents of the currently inserted beaker only if machine is on & beaker has some reagents inside
* Arguments
@@ -142,23 +192,6 @@
for(var/datum/tgui/ui in src.open_uis)
ui.send_update()
-/obj/machinery/chem_heater/examine(mob/user)
- . = ..()
- if(in_range(user, src) || isobserver(user))
- . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.")
- if(!QDELETED(beaker))
- . += span_notice("It has a beaker of [beaker.reagents.total_volume] units capacity.")
- if(beaker.reagents.is_reacting)
- . += span_notice("Its contents are currently reacting.")
- else
- . += span_warning("There is no beaker inserted.")
- . += span_notice("Its heating is turned [on ? "On" : "Off"].")
- . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.")
- if(panel_open)
- . += span_notice("Its panel is open and can now be [EXAMINE_HINT("pried")] apart.")
- else
- . += span_notice("Its panel can be [EXAMINE_HINT("pried")] open")
-
/obj/machinery/chem_heater/process(seconds_per_tick)
//is_reacting is handled in reaction_step()
if(QDELETED(beaker) || beaker.reagents.is_reacting)
@@ -172,39 +205,6 @@
for(var/datum/tgui/ui in src.open_uis)
ui.send_update()
-/obj/machinery/chem_heater/wrench_act(mob/living/user, obj/item/tool)
- . = ITEM_INTERACT_BLOCKING
- if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
- return ITEM_INTERACT_SUCCESS
-
-/obj/machinery/chem_heater/screwdriver_act(mob/living/user, obj/item/tool)
- . = ITEM_INTERACT_BLOCKING
- if(default_deconstruction_screwdriver(user, "mixer0b", "[base_icon_state][beaker ? 1 : 0]b", tool))
- return ITEM_INTERACT_SUCCESS
-
-/obj/machinery/chem_heater/crowbar_act(mob/living/user, obj/item/tool)
- . = ITEM_INTERACT_BLOCKING
- if(default_deconstruction_crowbar(tool))
- return ITEM_INTERACT_SUCCESS
-
-/obj/machinery/chem_heater/attackby(obj/item/held_item, mob/user, params)
- if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
- return ..()
-
- if(beaker)
- if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe))
- var/obj/item/reagent_containers/injector = held_item
- injector.afterattack(beaker, user, proximity_flag = TRUE)
- return TRUE
-
- if(is_reagent_container(held_item) && held_item.is_open_container())
- if(replace_beaker(user, held_item))
- ui_interact(user)
- balloon_alert(user, "beaker added!")
- return TRUE
-
- return ..()
-
/obj/machinery/chem_heater/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
diff --git a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm
index c23a86ca9dc..e9ffc91cd34 100644
--- a/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_mass_spec.dm
@@ -1,21 +1,16 @@
-
-#define BEAKER1 1
-#define BEAKER2 2
-
/obj/machinery/chem_mass_spec
name = "High-performance liquid chromatography machine"
- desc = {"This machine can separate reagents based on charge, meaning it can clean reagents of some of their impurities, unlike the Chem Master 3000.
-By selecting a range in the mass spectrograph certain reagents will be transferred from one beaker to another, which will clean it of any impurities up to a certain amount.
-This will not clean any inverted reagents. Inverted reagents will still be correctly detected and displayed on the scanner, however.
-\nLeft click with a beaker to add it to the input slot, Right click with a beaker to add it to the output slot. Alt + left/right click can let you quickly remove the corresponding beaker."}
- density = TRUE
- layer = BELOW_OBJ_LAYER
+ desc = "Allows you to purify reagents & seperate out inverse reagents"
icon = 'icons/obj/medical/chemical.dmi'
icon_state = "HPLC"
base_icon_state = "HPLC"
+ density = TRUE
+ interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_REQUIRES_ANCHORED
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.2
resistance_flags = FIRE_PROOF | ACID_PROOF
+ processing_flags = START_PROCESSING_MANUALLY
circuit = /obj/item/circuitboard/machine/chem_mass_spec
+
///If we're processing reagents or not
var/processing_reagents = FALSE
///Time we started processing + the delay
@@ -37,157 +32,244 @@ This will not clean any inverted reagents. Inverted reagents will still be corre
/obj/machinery/chem_mass_spec/Initialize(mapload)
. = ..()
+
ADD_TRAIT(src, TRAIT_DO_NOT_SPLASH, INNATE_TRAIT)
+
if(mapload)
beaker2 = new /obj/item/reagent_containers/cup/beaker/large(src)
- AddElement( \
- /datum/element/contextual_screentip_bare_hands, \
- lmb_text = "Add input beaker", \
- rmb_text = "Add output beaker", \
- )
+ register_context()
/obj/machinery/chem_mass_spec/Destroy()
QDEL_NULL(beaker1)
QDEL_NULL(beaker2)
return ..()
-/obj/machinery/chem_mass_spec/RefreshParts()
- . = ..()
- cms_coefficient = 1
- for(var/datum/stock_part/micro_laser/laser in component_parts)
- cms_coefficient /= laser.tier
-
/obj/machinery/chem_mass_spec/on_deconstruction(disassembled)
- if(beaker1)
- beaker1.forceMove(drop_location())
- beaker1 = null
- if(beaker2)
- beaker2.forceMove(drop_location())
- beaker2 = null
+ var/location = drop_location()
+ beaker1?.forceMove(location)
+ beaker2?.forceMove(location)
+
+/obj/machinery/chem_mass_spec/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = NONE
+
+ if(!QDELETED(beaker1))
+ context[SCREENTIP_CONTEXT_ALT_LMB] = "Eject input beaker"
+ . = CONTEXTUAL_SCREENTIP_SET
+ if(!QDELETED(beaker2))
+ context[SCREENTIP_CONTEXT_ALT_RMB] = "Eject output beaker"
+ . = CONTEXTUAL_SCREENTIP_SET
+
+ if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
+ return
+
+ if(is_reagent_container(held_item))
+ if(QDELETED(beaker1))
+ context[SCREENTIP_CONTEXT_LMB] = "Insert input beaker"
+ else
+ context[SCREENTIP_CONTEXT_LMB] = "Replace input beaker"
+
+ if(QDELETED(beaker2))
+ context[SCREENTIP_CONTEXT_RMB] = "Insert output beaker"
+ else
+ context[SCREENTIP_CONTEXT_RMB] = "Replace output beaker"
+
+ return CONTEXTUAL_SCREENTIP_SET
+
+ if(held_item.tool_behaviour == TOOL_WRENCH)
+ context[SCREENTIP_CONTEXT_LMB] = "[anchored ? "Un" : ""]anchor"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(panel_open && held_item.tool_behaviour == TOOL_CROWBAR)
+ context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/machinery/chem_mass_spec/examine(mob/user)
+ . = ..()
+
+ if(!QDELETED(beaker1))
+ . += span_notice("Input beaker of [beaker1.reagents.maximum_volume]u capacity is inserted.")
+ . += span_notice("Its Input beaker Can be ejected with [EXAMINE_HINT("LMB Alt")] click.")
+ else
+ . += span_warning("Its missing an input beaker. insert with [EXAMINE_HINT("Left Click")].")
+ if(!QDELETED(beaker2))
+ . += span_notice("Output beaker of [beaker2.reagents.maximum_volume]u capacity is inserted.")
+ . += span_notice("Its Output beaker can be ejected with [EXAMINE_HINT("RMB Alt")] click.")
+ else
+ . += span_warning("Its missing an output beaker, insert with [EXAMINE_HINT("Right Click")].")
+
+ if(anchored)
+ . += span_notice("Its [EXAMINE_HINT("anchored")] in place.")
+ else
+ . += span_warning("Needs to be [EXAMINE_HINT("wrenched")] to use.")
+ . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] [panel_open ? "closed" : "open"].")
+ if(panel_open)
+ . += span_notice("It can be [EXAMINE_HINT("pried")] apart.")
/obj/machinery/chem_mass_spec/update_overlays()
. = ..()
+
if(panel_open)
. += mutable_appearance(icon, "[base_icon_state]_panel-o")
-
-/obj/machinery/chem_mass_spec/wrench_act(mob/living/user, obj/item/tool)
- . = ..()
- default_unfasten_wrench(user, tool)
- return ITEM_INTERACT_SUCCESS
-
-/* beaker swapping/attack code */
-/obj/machinery/chem_mass_spec/attackby(obj/item/item, mob/user, params)
- if(processing_reagents)
- to_chat(user, " The [src] is currently processing a batch!")
- return ..()
-
- if(default_deconstruction_screwdriver(user, icon_state, icon_state, item))
- update_appearance()
return
- if(is_reagent_container(item) && !(item.item_flags & ABSTRACT) && item.is_open_container())
- var/obj/item/reagent_containers/beaker = item
- . = TRUE //no afterattack
- if(!user.transferItemToLoc(beaker, src))
- return
- replace_beaker(user, BEAKER1, beaker)
- to_chat(user, span_notice("You add [beaker] to [src]."))
- update_appearance()
- ui_interact(user)
- return
- ..()
-
-/obj/machinery/chem_mass_spec/attackby_secondary(obj/item/item, mob/user, params)
- . = ..()
-
- if(processing_reagents)
- to_chat(user, " The [src] is currently processing a batch!")
- return
-
- if(default_deconstruction_crowbar(item))
- return
-
- if(is_reagent_container(item) && !(item.item_flags & ABSTRACT) && item.is_open_container())
- var/obj/item/reagent_containers/beaker = item
- if(!user.transferItemToLoc(beaker, src))
- return
- replace_beaker(user, BEAKER2, beaker)
- to_chat(user, span_notice("You add [beaker] to [src]."))
- ui_interact(user)
- . = SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
-
- update_appearance()
-
-/obj/machinery/chem_mass_spec/AltClick(mob/living/user)
- . = ..()
- if(processing_reagents)
- to_chat(user, " The [src] is currently processing a batch!")
- return
- if(!can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
- return ..()
- replace_beaker(user, BEAKER1)
-
-/obj/machinery/chem_mass_spec/alt_click_secondary(mob/living/user)
- . = ..()
- if(processing_reagents)
- to_chat(user, " The [src] is currently processing a batch!")
- return
- if(!can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
- return
- replace_beaker(user, BEAKER2)
-
-///Gee how come you get two beakers?
-/*
- * Similar to other replace beaker procs, except now there are two of them!
- * When passed a beaker along with a position define it will swap a beaker in that slot (if there is one) with the beaker the machine is bonked with
- *
- * arguments:
- * * user - The one bonking the machine
- * * target beaker - the define (BEAKER1/BEAKER2) of what position to replace
- * * new beaker - the new beaker to add/replace the slot with
- */
-/obj/machinery/chem_mass_spec/proc/replace_beaker(mob/living/user, target_beaker, obj/item/reagent_containers/new_beaker)
- if(!user)
- return FALSE
- switch(target_beaker)
- if(BEAKER1)
- if(beaker1)
- try_put_in_hand(beaker1, user)
- beaker1 = null
- beaker1 = new_beaker
- lower_mass_range = calculate_smallest_mass()
- upper_mass_range = calculate_largest_mass()
- if(BEAKER2)
- if(beaker2)
- try_put_in_hand(beaker2, user)
- beaker2 = null
- beaker2 = new_beaker
- update_appearance()
- return TRUE
-
-/* Icon code */
-
-/obj/machinery/chem_mass_spec/update_icon_state()
- if(powered())
- icon_state = "HPLC_on"
- else
- icon_state = "HPLC"
- return ..()
-
-/obj/machinery/chem_mass_spec/update_overlays()
- . = ..()
- if(beaker1)
+ if(!QDELETED(beaker1))
. += "HPLC_beaker1"
- if(beaker2)
+ if(!QDELETED(beaker2))
. += "HPLC_beaker2"
- if(powered())
+
+ if(is_operational && !panel_open && anchored && !(machine_stat & (BROKEN | NOPOWER)))
if(processing_reagents)
. += "HPLC_graph_active"
else if (length(beaker1?.reagents.reagent_list))
. += "HPLC_graph_idle"
-/* UI Code */
+/obj/machinery/chem_mass_spec/update_icon_state()
+ if(is_operational && !panel_open && anchored && !(machine_stat & (BROKEN | NOPOWER)))
+ icon_state = "HPLC_on"
+ else
+ icon_state = "HPLC"
+ return ..()
+
+/obj/machinery/chem_mass_spec/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == beaker1)
+ beaker1 = null
+ if(gone == beaker2)
+ beaker2 = null
+
+/obj/machinery/chem_mass_spec/RefreshParts()
+ . = ..()
+
+ cms_coefficient = 1
+ for(var/datum/stock_part/micro_laser/laser in component_parts)
+ cms_coefficient /= laser.tier
+
+/obj/machinery/chem_mass_spec/item_interaction(mob/living/user, obj/item/item, list/modifiers, is_right_clicking)
+ if((item.item_flags & ABSTRACT) || (item.flags_1 & HOLOGRAM_1) || !can_interact(user) || !user.can_perform_action(src, FORBID_TELEKINESIS_REACH))
+ return ..()
+
+ if(is_reagent_container(item) && item.is_open_container())
+ if(processing_reagents)
+ balloon_alert(user, "still processing!")
+ return ITEM_INTERACT_BLOCKING
+
+ var/obj/item/reagent_containers/beaker = item
+ if(!user.transferItemToLoc(beaker, src))
+ return ITEM_INTERACT_BLOCKING
+
+ replace_beaker(user, !is_right_clicking, beaker)
+ to_chat(user, span_notice("You add [beaker] to [is_right_clicking ? "output" : "input"] slot."))
+ update_appearance()
+ ui_interact(user)
+ return ITEM_INTERACT_SUCCESS
+
+ return ..()
+
+/obj/machinery/chem_mass_spec/wrench_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(processing_reagents)
+ balloon_alert(user, "still processing!")
+ return .
+
+ if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/chem_mass_spec/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(processing_reagents)
+ balloon_alert(user, "still processing!")
+ return .
+
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, tool))
+ update_appearance()
+ return ITEM_INTERACT_SUCCESS
+
+/obj/machinery/chem_mass_spec/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(processing_reagents)
+ balloon_alert(user, "still processing!")
+ return .
+
+ if(default_deconstruction_crowbar(tool))
+ return ITEM_INTERACT_SUCCESS
+
+
+/**
+ * Computes either the lightest or heaviest reagent in the input beaker
+ * Arguments
+ *
+ * * smallest - TRUE to find lightest reagent, FALSE to find heaviest reagent
+ */
+/obj/machinery/chem_mass_spec/proc/calculate_mass(smallest = TRUE)
+ PRIVATE_PROC(TRUE)
+ SHOULD_BE_PURE(TRUE)
+
+ if(QDELETED(beaker1))
+ return 0
+
+ var/result = 0
+ for(var/datum/reagent/reagent as anything in beaker1?.reagents.reagent_list)
+ var/datum/reagent/target = reagent
+ if(!istype(reagent, /datum/reagent/inverse) && (reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem))
+ target = GLOB.chemical_reagents_list[reagent.inverse_chem]
+
+ if(!result)
+ result = target.mass
+ else
+ result = smallest ? min(result, reagent.mass) : max(result, reagent.mass)
+ return smallest ? FLOOR(result, 50) : CEILING(result, 50)
+
+/*
+ * Replaces a beaker in the machine, either input or output
+ * Arguments
+ *
+ * * user - The one bonking the machine
+ * * target beaker - the target beaker we are trying to replace
+ * * new beaker - the new beaker to add/replace the slot with
+ */
+/obj/machinery/chem_mass_spec/proc/replace_beaker(mob/living/user, is_input, obj/item/reagent_containers/new_beaker)
+ PRIVATE_PROC(TRUE)
+
+ if(is_input) //replace input beaker
+ if(!QDELETED(beaker1))
+ try_put_in_hand(beaker1, user)
+ beaker1 = new_beaker
+ lower_mass_range = calculate_mass(smallest = TRUE)
+ upper_mass_range = calculate_mass(smallest = FALSE)
+ estimate_time()
+
+ else //replace output beaker
+ if(!QDELETED(beaker2))
+ try_put_in_hand(beaker2, user)
+ beaker2 = new_beaker
+
+ update_appearance()
+
+///Computes time to purity reagents
+/obj/machinery/chem_mass_spec/proc/estimate_time()
+ PRIVATE_PROC(TRUE)
+
+ delay_time = 0
+ if(QDELETED(beaker1))
+ return
+
+ for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list)
+ //we don't bother about impure chems
+ if(istype(reagent, /datum/reagent/inverse) || (reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem))
+ continue
+ //out of our selected range
+ if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range)
+ continue
+ //already at max purity
+ if((initial(reagent.purity) - reagent.purity) <= 0)
+ continue
+ ///Roughly 10 - 30s?
+ delay_time += (((reagent.mass * reagent.volume) + (reagent.mass * reagent.get_inverse_purity() * 0.1)) * 0.0035) + 10
+
+ delay_time *= cms_coefficient
/obj/machinery/chem_mass_spec/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -196,209 +278,214 @@ This will not clean any inverted reagents. Inverted reagents will still be corre
ui.open()
/obj/machinery/chem_mass_spec/ui_data(mob/user)
- var/data = list()
- data["graphLowerRange"] = 0
- data["lowerRange"] = lower_mass_range
- data["upperRange"] = upper_mass_range
- data["processing"] = processing_reagents
- data["log"] = log
- data["beaker1"] = beaker1 ? TRUE : FALSE
- data["beaker2"] = beaker2 ? TRUE : FALSE
- if(processing_reagents)
- data["eta"] = delay_time - progress_time
- else
- data["eta"] = estimate_time()
+ . = list()
+ .["lowerRange"] = lower_mass_range
+ .["upperRange"] = upper_mass_range
+ .["processing"] = processing_reagents
+ .["eta"] = delay_time - progress_time
+ .["peakHeight"] = 0
- var/beakerContents[0]
- if(beaker1 && beaker1.reagents)
- for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list)
- var/in_range = TRUE
- if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)
- var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem]
- if(inverse_reagent.mass < lower_mass_range || inverse_reagent.mass > upper_mass_range)
- in_range = FALSE
- beakerContents.Add(list(list("name" = inverse_reagent.name, "volume" = round(reagent.volume, 0.01), "mass" = inverse_reagent.mass, "purity" = round(reagent.get_inverse_purity(), 0.000001)*100, "selected" = in_range, "color" = "#b60046", "type" = "Inverted")))
- data["peakHeight"] = max(data["peakHeight"], reagent.volume)
- continue
- if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range)
- in_range = FALSE
- ///We want to be sure that the impure chem appears after the parent chem in the list so that it always overshadows pure reagents
- beakerContents.Add(list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01), "mass" = reagent.mass, "purity" = round(reagent.purity, 0.000001)*100, "selected" = in_range, "color" = "#3cf096", "type" = "Clean")))
- data["peakHeight"] = max(data["peakHeight"], reagent.volume)
+ //input reagents
+ var/list/beaker1Data = null
+ if(!QDELETED(beaker1))
+ beaker1Data = list()
+ var/datum/reagents/beaker_1_reagents = beaker1.reagents
+ beaker1Data["currentVolume"] = beaker_1_reagents.total_volume
+ beaker1Data["maxVolume"] = beaker_1_reagents.maximum_volume
+ var/list/beakerContents = list()
+ for(var/datum/reagent/reagent as anything in beaker_1_reagents.reagent_list)
+ var/log = ""
+ var/datum/reagent/target = reagent
+ var/purity = target.purity
+ var/is_inverse = FALSE
- data["beaker1CurrentVolume"] = beaker1.reagents.total_volume
- data["beaker1MaxVolume"] = beaker1.reagents.maximum_volume
- data["beaker1Contents"] = beakerContents
- data["graphUpperRange"] = calculate_largest_mass() //+10 because of the range on the peak
+ if(istype(reagent, /datum/reagent/inverse))
+ log = "Too impure to use" //we don't bother about impure chems
+ is_inverse = TRUE
+ else if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)
+ purity = target.get_inverse_purity()
+ target = GLOB.chemical_reagents_list[reagent.inverse_chem]
+ log = "Too impure to use" //we don't bother about impure chems
+ is_inverse = TRUE
+ else
+ var/initial_purity = initial(reagent.purity)
+ if((initial_purity - reagent.purity) <= 0) //already at max purity
+ log = "Cannot purify above [round(initial_purity * 100)]%"
+ else
+ log = "Ready"
- beakerContents = list()
- if(beaker2 && beaker2.reagents)
- for(var/datum/reagent/reagent in beaker2.reagents.reagent_list)
- ///Normal stuff
- beakerContents.Add(list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01), "mass" = reagent.mass, "purity" = round(reagent.purity, 0.000001)*100, "color" = "#3cf096", "type" = "Clean", log = log[reagent.type])))
- data["beaker2CurrentVolume"] = beaker2.reagents.total_volume
- data["beaker2MaxVolume"] = beaker2.reagents.maximum_volume
- data["beaker2Contents"] = beakerContents
+ beakerContents += list(list(
+ "name" = target.name,
+ "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING),
+ "mass" = target.mass,
+ "purity" = round(purity * 100),
+ "type" = is_inverse ? "Inverted" : "Clean",
+ "log" = log
+ ))
+ .["peakHeight"] = max(.["peakHeight"], reagent.volume)
+ beaker1Data["contents"] = beakerContents
+ .["beaker1"] = beaker1Data
- return data
+ //+10 because of the range on the peak
+ .["graphUpperRange"] = calculate_mass(smallest = FALSE)
-/obj/machinery/chem_mass_spec/ui_act(action, params)
+ //output reagents
+ var/list/beaker2Data = null
+ if(!QDELETED(beaker2))
+ beaker2Data = list()
+ var/datum/reagents/beaker_2_reagents = beaker2.reagents
+ beaker2Data["currentVolume"] = beaker_2_reagents.total_volume
+ beaker2Data["maxVolume"] = beaker_2_reagents.maximum_volume
+ var/list/beakerContents = list()
+ for(var/datum/reagent/reagent as anything in beaker_2_reagents.reagent_list)
+ beakerContents += list(list(
+ "name" = reagent.name,
+ "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING),
+ "mass" = reagent.mass,
+ "purity" = round(reagent.purity * 100),
+ "type" = "Clean",
+ "log" = log[reagent.type]
+ ))
+ beaker2Data["contents"] = beakerContents
+ .["beaker2"] = beaker2Data
+
+/obj/machinery/chem_mass_spec/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
+
+ if(processing_reagents)
+ balloon_alert(ui.user, "still processing")
+ return ..()
+
switch(action)
if("activate")
- if(!beaker1 || !beaker2 || !is_operational)
- say("This [src] is missing an output beaker!")
+ if(QDELETED(beaker1))
+ say("Missing input beaker!")
return
- if(processing_reagents)
- say("You shouldn't be seeing this message! Please report this bug to https://github.com/tgstation/tgstation/issues . Thank you!")
- stack_trace("Someone managed to break the HPLC and tried to get it to activate when it's already activated!")
+ if(QDELETED(beaker2))
+ say("Missing output beaker!")
return
- processing_reagents = TRUE
- estimate_time()
+
+ //adjust timer for purification
progress_time = 0
- update_appearance()
+ estimate_time()
+ if(delay_time <= 0)
+ say("No work to be done!")
+ return
+
+ //start the purification process
+ processing_reagents = TRUE
begin_processing()
- . = TRUE
+ update_appearance()
+
+ return TRUE
+
if("leftSlider")
- if(!is_operational || processing_reagents)
+ var/value = params["value"]
+ if(isnull(value))
return
- var/current_center = (lower_mass_range + upper_mass_range)/2
- lower_mass_range = clamp(params["value"], calculate_smallest_mass(), current_center)
- . = TRUE
+
+ value = text2num(value)
+ if(isnull(value))
+ return
+
+ lower_mass_range = clamp(value, calculate_mass(smallest = TRUE), (lower_mass_range + upper_mass_range) / 2)
+ estimate_time()
+ return TRUE
+
if("rightSlider")
- if(!is_operational || processing_reagents)
+ var/value = params["value"]
+ if(isnull(value))
return
- var/current_center = (lower_mass_range + upper_mass_range)/2
- upper_mass_range = clamp(params["value"], current_center, calculate_largest_mass())
- . = TRUE
+
+ value = text2num(value)
+ if(isnull(value))
+ return
+
+ upper_mass_range = clamp(value, (lower_mass_range + upper_mass_range) / 2, calculate_mass(smallest = FALSE))
+ estimate_time()
+ return TRUE
+
if("centerSlider")
- if(!is_operational || processing_reagents)
+ var/value = params["value"]
+ if(isnull(value))
return
- var/current_center = (lower_mass_range + upper_mass_range)/2
- var/delta_center = current_center - params["value"]
- var/lowest = calculate_smallest_mass()
- var/highest = calculate_largest_mass()
+
+ value = text2num(value)
+ if(isnull(value))
+ return
+
+ var/delta_center = ((lower_mass_range + upper_mass_range) / 2) - params["value"]
+ var/lowest = calculate_mass(smallest = TRUE)
+ var/highest = calculate_mass(smallest = FALSE)
lower_mass_range = clamp(lower_mass_range - delta_center, lowest, highest)
upper_mass_range = clamp(upper_mass_range - delta_center, lowest, highest)
- . = TRUE
+ estimate_time()
+
+ return TRUE
+
if("eject1")
- if(processing_reagents)
- return
- replace_beaker(usr, BEAKER1)
- . = TRUE
+ replace_beaker(ui.user, TRUE)
+ return TRUE
+
if("eject2")
- if(processing_reagents)
- return
- replace_beaker(usr, BEAKER2)
- . = TRUE
+ replace_beaker(ui.user, FALSE)
+ return TRUE
-/* processing procs */
-
-///Increments time if it's progressing - if it's past time then it purifies and stops processing
-/obj/machinery/chem_mass_spec/process(seconds_per_tick)
+/obj/machinery/chem_mass_spec/AltClick(mob/living/user)
. = ..()
- if(!is_operational)
- return FALSE
+ if(!can_interact(user))
+ return
+ if(processing_reagents)
+ balloon_alert(user, "still processing!")
+ return ..()
+ replace_beaker(user, TRUE)
+
+/obj/machinery/chem_mass_spec/alt_click_secondary(mob/living/user)
+ . = ..()
+ if(!can_interact(user))
+ return
+ if(processing_reagents)
+ balloon_alert(user, "still processing!")
+ return ..()
+ replace_beaker(user, FALSE)
+
+/obj/machinery/chem_mass_spec/process(seconds_per_tick)
if(!processing_reagents)
- return TRUE
+ return PROCESS_KILL
+
+ if(!is_operational || panel_open || !anchored || (machine_stat & (BROKEN | NOPOWER)))
+ return
+
use_power(active_power_usage)
+
+ progress_time += seconds_per_tick
if(progress_time >= delay_time)
processing_reagents = FALSE
progress_time = 0
- purify_reagents()
- end_processing()
+
+ log.Cut()
+ for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list)
+ //we don't bother about impure chems
+ if(istype(reagent, /datum/reagent/inverse) || (reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem))
+ continue
+ //out of our selected range
+ if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range)
+ continue
+ //already at max purity
+ var/delta_purity = initial(reagent.purity) - reagent.purity
+ if(delta_purity <= 0)
+ continue
+ //add the purified reagent. More impure reagents will yield smaller amounts
+ var/product_vol = reagent.volume
+ beaker1.reagents.remove_reagent(reagent.type, product_vol)
+ beaker2.reagents.add_reagent(reagent.type, product_vol * (1 - delta_purity), reagtemp = beaker1.reagents.chem_temp, added_purity = initial(reagent.purity), added_ph = reagent.ph)
+ log[reagent.type] = "Purified to [initial(reagent.purity) * 100]%"
+
+ //recompute everything
+ lower_mass_range = calculate_mass(smallest = TRUE)
+ upper_mass_range = calculate_mass(smallest = FALSE)
+ estimate_time()
update_appearance()
- return TRUE
- progress_time += seconds_per_tick
- return FALSE
-
-/*
- * Processing through the reagents in beaker 1
- * For all the reagents within the selected range - we will then purify them up to their initial purity (usually 75%). It will take away the relative reagent volume from the sum volume of the reagent however.
- * If there are any inverted reagents - then it will instead just create a new reagent of the inverted type. This doesn't really do anything other than change the name of it,
- * As it processes through the reagents, it saves what changes were applied to each reagent in a log var to show the results at the end
- */
-/obj/machinery/chem_mass_spec/proc/purify_reagents()
- log = list()
- for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list)
- //Inverse first
- var/volume = reagent.volume
- if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)
- var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem]
- if(inverse_reagent.mass < lower_mass_range || inverse_reagent.mass > upper_mass_range)
- continue
- log += list(inverse_reagent.type = "Cannot purify inverted") //Might as well make it do something - just updates the reagent's name
- beaker2.reagents.add_reagent(reagent.inverse_chem, volume, reagtemp = beaker1.reagents.chem_temp, added_purity = reagent.get_inverse_purity())
- beaker1.reagents.remove_reagent(reagent.type, volume)
- continue
-
- if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range)
- continue
-
- var/delta_purity = initial(reagent.purity) - reagent.purity
- if(delta_purity <= 0)//As pure as we can be - so lets not add more than we need
- log += list(reagent.type = "Can't purify over [initial(reagent.purity)*100]%")
- beaker2.reagents.add_reagent(reagent.type, volume, reagtemp = beaker1.reagents.chem_temp, added_purity = reagent.purity, added_ph = reagent.ph)
- beaker1.reagents.remove_reagent(reagent.type, volume)
- continue
-
- var/product_vol = reagent.volume * (1-delta_purity)
- beaker2.reagents.add_reagent(reagent.type, product_vol, reagtemp = beaker1.reagents.chem_temp, added_purity = initial(reagent.purity), added_ph = reagent.ph)
- beaker1.reagents.remove_reagent(reagent.type, reagent.volume)
- log += list(reagent.type = "Purified to [initial(reagent.purity)*100]%")
-
-/* Mass spec graph calcs */
-
-///Returns the largest mass to the nearest 50 (rounded up)
-/obj/machinery/chem_mass_spec/proc/calculate_largest_mass()
- if(!beaker1?.reagents)
- return 0
- var/max_mass = 0
- for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list)
- if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)
- var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem]
- max_mass = max(max_mass, inverse_reagent.mass)
- continue
- max_mass = max(max_mass, reagent.mass)
- return CEILING(max_mass, 50)
-
-///Returns the smallest mass to the nearest 50 (rounded down)
-/obj/machinery/chem_mass_spec/proc/calculate_smallest_mass()
- if(!beaker1?.reagents)
- return 0
- var/min_mass = 0
- for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list)
- if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)
- var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem]
- min_mass = min(min_mass, inverse_reagent.mass)
- continue
- min_mass = min(min_mass, reagent.mass)
- return FLOOR(min_mass, 50)
-
-/*
- * Estimates how long the highlighted range will take to process
- * The time will increase based off the reagent's volume, mass and purity.
- * In most cases this is between 10 to 30s for a single reagent.
- * This is why having a higher mass for a reagent is a balancing tool.
- */
-/obj/machinery/chem_mass_spec/proc/estimate_time()
- if(!beaker1?.reagents)
- return 0
- var/time = 0
- for(var/datum/reagent/reagent as anything in beaker1.reagents.reagent_list)
- if(reagent.inverse_chem_val > reagent.purity && reagent.inverse_chem)
- var/datum/reagent/inverse_reagent = GLOB.chemical_reagents_list[reagent.inverse_chem]
- if(inverse_reagent.mass < lower_mass_range || inverse_reagent.mass > upper_mass_range)
- continue
- time += (((inverse_reagent.mass * reagent.volume) + (inverse_reagent.mass * reagent.purity * 0.1)) * 0.003) + 10 ///Roughly 10 - 30s?
- continue
- if(reagent.mass < lower_mass_range || reagent.mass > upper_mass_range)
- continue
- time += (((reagent.mass * reagent.volume) + (reagent.mass * reagent.get_inverse_purity() * 0.1)) * 0.0035) + 10 ///Roughly 10 - 30s?
- delay_time = (time * cms_coefficient)
- return delay_time
-
-#undef BEAKER1
-#undef BEAKER2
+ return PROCESS_KILL
diff --git a/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm
index 791feb80039..a6113d2f0c6 100644
--- a/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm
+++ b/code/modules/reagents/chemistry/machinery/portable_chem_mixer.dm
@@ -105,7 +105,7 @@
/obj/item/storage/portable_chem_mixer/ex_act(severity, target)
return severity > EXPLODE_LIGHT ? ..() : FALSE
-/obj/item/storage/portable_chem_mixer/attackby(obj/item/weapon, mob/user, params)
+/obj/item/storage/portable_chem_mixer/item_interaction(mob/living/user, obj/item/weapon, list/modifiers, is_right_clicking)
if (!atom_storage.locked || \
(weapon.item_flags & ABSTRACT) || \
(weapon.flags_1 & HOLOGRAM_1) || \
@@ -116,7 +116,7 @@
replace_beaker(user, weapon)
update_appearance()
- return TRUE
+ return ITEM_INTERACT_SUCCESS
/**
* Replaces the beaker of the portable chemical mixer with another beaker, or simply adds the new beaker if none is in currently
@@ -185,11 +185,11 @@
beaker_data["maxVolume"] = beaker.volume
beaker_data["transferAmounts"] = beaker.possible_transfer_amounts
beaker_data["pH"] = round(beaker.reagents.ph, 0.01)
- beaker_data["currentVolume"] = round(beaker.reagents.total_volume, 0.01)
+ beaker_data["currentVolume"] = round(beaker.reagents.total_volume, CHEMICAL_VOLUME_ROUNDING)
var/list/beakerContents = list()
if(length(beaker.reagents.reagent_list))
for(var/datum/reagent/reagent in beaker.reagents.reagent_list)
- beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, 0.01))) // list in a list because Byond merges the first list...
+ beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING))) // list in a list because Byond merges the first list...
beaker_data["contents"] = beakerContents
.["beaker"] = beaker_data
diff --git a/tgui/packages/tgui/interfaces/MassSpec.jsx b/tgui/packages/tgui/interfaces/MassSpec.jsx
deleted file mode 100644
index 29d7fb8269d..00000000000
--- a/tgui/packages/tgui/interfaces/MassSpec.jsx
+++ /dev/null
@@ -1,389 +0,0 @@
-import { round } from 'common/math';
-
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- Dimmer,
- Icon,
- Section,
- Slider,
- Table,
-} from '../components';
-import { Window } from '../layouts';
-
-export const MassSpec = (props) => {
- const { act, data } = useBackend();
- const {
- processing,
- lowerRange,
- upperRange,
- graphUpperRange,
- graphLowerRange,
- eta,
- beaker1CurrentVolume,
- beaker2CurrentVolume,
- beaker1MaxVolume,
- beaker2MaxVolume,
- peakHeight,
- beaker1,
- beaker2,
- beaker1Contents = [],
- beaker2Contents = [],
- } = data;
-
- const centerValue = (lowerRange + upperRange) / 2;
-
- return (
-
-
- {!!processing && (
-
-
- {' Purifying... ' + round(eta) + 's'}
-
- )}
- act('activate')}
- />
- }
- >
- {(beaker1Contents.length && (
-
- )) || Please insert an input beaker with reagents!}
-
-
-
- {!!beaker1MaxVolume && (
-
- {beaker1CurrentVolume} / {beaker1MaxVolume} units
-
- )}
-
-
- {!!beaker2MaxVolume && (
-
- {beaker2CurrentVolume} / {beaker2MaxVolume} units
-
- )}
-
-
-
- );
-};
-
-const BeakerMassProfile = (props) => {
- const { loaded, details, beaker = [] } = props;
-
- return (
-
- {(!loaded && No beaker loaded.) ||
- (beaker.length === 0 && Beaker is empty.) || (
-
-
-
- Reagent
-
-
- Volume
-
-
- Mass
-
-
- Purity
-
-
- Type
-
- {!!details && (
-
- Results
-
- )}
-
- {beaker.map((reagent) => (
-
-
- {reagent.name}
-
-
- {reagent.volume}
-
-
- {reagent.mass}
-
-
- {`${reagent.purity}%`}
-
-
- ▮{reagent.type}
-
- {!!details && {reagent.log}}
-
- ))}
-
- )}
-
- );
-};
-
-const MassSpectroscopy = (props) => {
- const { act, data } = useBackend();
- const {
- lowerRange,
- centerValue,
- upperRange,
- graphUpperRange,
- graphLowerRange,
- maxAbsorbance,
- reagentPeaks = [],
- } = props;
-
- const deltaRange = graphUpperRange - graphLowerRange;
-
- const graphIncrement = deltaRange * 0.2;
-
- return (
- <>
-
-
-
-
- round(value)}
- width={(centerValue / graphUpperRange) * 400 + 'px'}
- value={lowerRange}
- minValue={graphLowerRange}
- maxValue={centerValue}
- color={'invisible'}
- onDrag={(e, value) =>
- act('leftSlider', {
- value: value,
- })
- }
- >
- {' '}
-
- round(value)}
- step={graphUpperRange / 400}
- width={400 - (centerValue / graphUpperRange) * 400 + 'px'}
- value={upperRange}
- minValue={centerValue}
- maxValue={graphUpperRange}
- color={'invisible'}
- onDrag={(e, value) =>
- act('rightSlider', {
- value: value,
- })
- }
- >
- {' '}
-
-
- round(value)}
- width={400 + 'px'}
- minValue={graphLowerRange + 1}
- maxValue={graphUpperRange - 1}
- color={'invisible'}
- onDrag={(e, value) =>
- act('centerSlider', {
- value: value,
- })
- }
- >
- {' '}
-
-
-
- >
- );
-};
diff --git a/tgui/packages/tgui/interfaces/MassSpec.tsx b/tgui/packages/tgui/interfaces/MassSpec.tsx
new file mode 100644
index 00000000000..672157ad8d0
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/MassSpec.tsx
@@ -0,0 +1,456 @@
+import { round } from 'common/math';
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../backend';
+import {
+ Box,
+ Button,
+ Dimmer,
+ Icon,
+ Section,
+ Slider,
+ Table,
+} from '../components';
+import { Window } from '../layouts';
+
+type Reagent = {
+ name: string;
+ volume: number;
+ mass: number;
+ purity: number;
+ type: string;
+ log: string;
+};
+
+type Beaker = {
+ currentVolume: number;
+ maxVolume: number;
+ contents: Reagent[];
+};
+
+type Data = {
+ lowerRange: number;
+ upperRange: number;
+ processing: BooleanLike;
+ eta: number;
+ graphUpperRange: number;
+ peakHeight: number;
+ beaker1: Beaker;
+ beaker2: Beaker;
+};
+
+const GRAPH_MAX_WIDTH = 1060;
+const GRAPH_MAX_HEIGHT = 250;
+
+export const MassSpec = (props) => {
+ const { act, data } = useBackend();
+ const {
+ processing,
+ lowerRange,
+ upperRange,
+ graphUpperRange,
+ eta,
+ peakHeight,
+ beaker1,
+ beaker2,
+ } = data;
+
+ const centerValue = (lowerRange + upperRange) / 2;
+ const beaker_1_has_contents = beaker1?.contents?.length > 0;
+
+ return (
+
+
+ {!!processing && (
+
+
+ {' Purifying... ' + round(eta, 0) + 's'}
+
+ )}
+ act('activate')}
+ >
+ Start
+
+ }
+ >
+ {(beaker_1_has_contents && (
+
+ )) || Please insert an input beaker with reagents!}
+
+
+
+ {
+
+ {beaker1.currentVolume} / {beaker1.maxVolume} units
+
+ }
+
+ >
+ )
+ }
+ >
+
+ {!!beaker_1_has_contents && (
+ {'Eta of selection: ' + round(eta, 0) + ' seconds'}
+ )}
+
+
+ {
+
+ {beaker2.currentVolume} / {beaker2.maxVolume} units
+
+ }
+
+ >
+ )
+ }
+ >
+
+
+
+
+ );
+};
+
+type ProfileProps = {
+ lowerRange: number;
+ upperRange: number;
+ beaker: Beaker;
+};
+
+const BeakerMassProfile = (props: ProfileProps) => {
+ const { lowerRange, upperRange, beaker } = props;
+
+ return (
+
+ {(!beaker && No beaker loaded.) ||
+ (beaker.contents.length === 0 && (
+ Beaker is empty.
+ )) || (
+
+
+
+ Reagent
+
+
+ Mass
+
+
+ Volume
+
+
+ Purity
+
+
+ Type
+
+
+ Status
+
+
+ {beaker.contents.map((reagent) => {
+ const selected =
+ reagent.mass >= lowerRange && reagent.mass <= upperRange;
+ const color = reagent.type === 'Inverted' ? '#b60046' : '#3cf096';
+
+ return (
+
+
+ {reagent.name}
+
+
+ {reagent.mass}
+
+
+ {reagent.volume}
+
+
+ {`${reagent.purity}%`}
+
+
+ ▮{reagent.type}
+
+ {{reagent.log}}
+
+ );
+ })}
+
+ )}
+
+ );
+};
+
+type SpectroscopyProps = {
+ lowerRange: number;
+ centerValue: number;
+ upperRange: number;
+ graphUpperRange: number;
+ maxAbsorbance: number;
+ reagentPeaks: Reagent[];
+};
+
+const MassSpectroscopy = (props: SpectroscopyProps) => {
+ const { act } = useBackend();
+ const {
+ lowerRange,
+ centerValue,
+ upperRange,
+ graphUpperRange,
+ maxAbsorbance,
+ reagentPeaks = [],
+ } = props;
+
+ const graphLowerRange = 0;
+ const deltaRange = graphUpperRange - graphLowerRange;
+ const graphIncrement = deltaRange * 0.2;
+
+ const base_line = GRAPH_MAX_HEIGHT * 0.85;
+ const base_width = GRAPH_MAX_WIDTH - 123;
+ const x_scale = base_width / GRAPH_MAX_WIDTH;
+ const y_scale = base_line / GRAPH_MAX_HEIGHT;
+
+ return (
+
+
+
+ {/* Sliders */}
+ round(value, 2)}
+ width={(centerValue / graphUpperRange) * base_width + 'px'}
+ value={lowerRange}
+ minValue={graphLowerRange}
+ maxValue={centerValue}
+ color={'invisible'}
+ onDrag={(e, value) =>
+ act('leftSlider', {
+ value: value,
+ })
+ }
+ />
+ round(value, 2)}
+ step={graphUpperRange / base_width}
+ width={base_width - (centerValue / graphUpperRange) * base_width + 'px'}
+ value={upperRange}
+ minValue={centerValue}
+ maxValue={graphUpperRange}
+ color={'invisible'}
+ onDrag={(e, value) =>
+ act('rightSlider', {
+ value: value,
+ })
+ }
+ />
+ round(value, 2)}
+ width={base_width + 'px'}
+ minValue={graphLowerRange + 1}
+ maxValue={graphUpperRange - 1}
+ color={'invisible'}
+ onDrag={(e, value) =>
+ act('centerSlider', {
+ value: value,
+ })
+ }
+ />
+
+ );
+};