mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-19 20:15:47 +01:00
General maintainence for HPLC (#81613)
## About The Pull Request
Moves around code for portable chem mixer & chem heater. No functional
changes, As for the HPLC
**1. Qol**
- Adds examines & screentips for
1) Screwdriver, crowbar & wrench acts
2) Inserting & replacing input/output beakers
3) Alt click ejecting input/output beakers
- Balloon alerts replaces chat messages to reduce spam.
- HPLC will now display the status of each reagent if it can be purified
or not before starting the refining process thus saving you the headache
of pressing the start button only to waste power & time & get no work
done
**2. Code Improvements**
- Moved screwdriver, wrench & crowbar acts out of `attackby()` into
their respective tool act PROCS
- Merged procs `calculate_largest_mass()` & ` calculate_smallest_mass()`
procs into a single proc `calculate_mass()` with a boolean var to decide
if we want the smallest or largest mass reagent
- Computes estimated time only when UI changes & not every ui update
**3. Fixes**
- HPLC won't accept hologram or abstract items
- HPLC now displays off icon state & pauses processing when the machine
is either opened, powered off, unanchored or broken
- HPLC will ignore reagents that are either already at max purity or are
inverse thus lowering eta & work done to purify your remaining reagents
**4. Refactor**
- Converted UI to typescript, moved vars such as `in_range` & `color` to
the client side to reduce data sent.
This commit is contained in:
@@ -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 <b>[heater_coefficient * 1000]%</b> 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 <b>[heater_coefficient * 1000]%</b> 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 <b>[heater_coefficient * 1000]%</b> 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 <b>[heater_coefficient * 1000]%</b> 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)
|
||||
|
||||
@@ -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, "<span class='notice'> 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, "<span class='notice'> 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, "<span class='notice'> 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, "<span class='notice'> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<Window width={490} height={650}>
|
||||
<Window.Content scrollable>
|
||||
{!!processing && (
|
||||
<Dimmer fontSize="32px">
|
||||
<Icon name="cog" spin={1} />
|
||||
{' Purifying... ' + round(eta) + 's'}
|
||||
</Dimmer>
|
||||
)}
|
||||
<Section
|
||||
title="Mass Spectroscopy"
|
||||
buttons={
|
||||
<Button
|
||||
icon="power-off"
|
||||
content="Start"
|
||||
disabled={!!processing || !beaker1Contents.length || !beaker2}
|
||||
tooltip={
|
||||
!beaker1Contents.length
|
||||
? 'Missing input reagents!'
|
||||
: !beaker2
|
||||
? 'Missing an output beaker!'
|
||||
: 'Begin purifying'
|
||||
}
|
||||
tooltipPosition="left"
|
||||
onClick={() => act('activate')}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{(beaker1Contents.length && (
|
||||
<MassSpectroscopy
|
||||
lowerRange={lowerRange}
|
||||
centerValue={centerValue}
|
||||
upperRange={upperRange}
|
||||
graphLowerRange={graphLowerRange}
|
||||
graphUpperRange={graphUpperRange}
|
||||
maxAbsorbance={peakHeight}
|
||||
reagentPeaks={beaker1Contents}
|
||||
/>
|
||||
)) || <Box>Please insert an input beaker with reagents!</Box>}
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Input beaker"
|
||||
buttons={
|
||||
!!beaker1Contents && (
|
||||
<>
|
||||
{!!beaker1MaxVolume && (
|
||||
<Box inline color="label" mr={2}>
|
||||
{beaker1CurrentVolume} / {beaker1MaxVolume} units
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
disabled={!beaker1}
|
||||
onClick={() => act('eject1')}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<BeakerMassProfile loaded={!!beaker1} beaker={beaker1Contents} />
|
||||
{!!beaker1Contents.length && (
|
||||
<Box>{'Eta of selection: ' + round(eta) + ' seconds'}</Box>
|
||||
)}
|
||||
</Section>
|
||||
<Section
|
||||
title="Output beaker"
|
||||
buttons={
|
||||
!!beaker2Contents && (
|
||||
<>
|
||||
{!!beaker2MaxVolume && (
|
||||
<Box inline color="label" mr={2}>
|
||||
{beaker2CurrentVolume} / {beaker2MaxVolume} units
|
||||
</Box>
|
||||
)}
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
disabled={!beaker2}
|
||||
onClick={() => act('eject2')}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<BeakerMassProfile
|
||||
loaded={!!beaker2}
|
||||
beaker={beaker2Contents}
|
||||
details
|
||||
/>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const BeakerMassProfile = (props) => {
|
||||
const { loaded, details, beaker = [] } = props;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{(!loaded && <Box color="label">No beaker loaded.</Box>) ||
|
||||
(beaker.length === 0 && <Box color="label">Beaker is empty.</Box>) || (
|
||||
<Table className="candystripe">
|
||||
<Table.Row>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Reagent
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Volume
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Mass
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Purity
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Type
|
||||
</Table.Cell>
|
||||
{!!details && (
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Results
|
||||
</Table.Cell>
|
||||
)}
|
||||
</Table.Row>
|
||||
{beaker.map((reagent) => (
|
||||
<Table.Row key={reagent.name}>
|
||||
<Table.Cell
|
||||
collapsing
|
||||
color={reagent.selected ? 'green' : 'default'}
|
||||
>
|
||||
{reagent.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
collapsing
|
||||
color={reagent.selected ? 'green' : 'default'}
|
||||
>
|
||||
{reagent.volume}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
collapsing
|
||||
color={reagent.selected ? 'green' : 'default'}
|
||||
>
|
||||
{reagent.mass}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
collapsing
|
||||
color={reagent.selected ? 'green' : 'default'}
|
||||
>
|
||||
{`${reagent.purity}%`}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing color={reagent.color}>
|
||||
▮{reagent.type}
|
||||
</Table.Cell>
|
||||
{!!details && <Table.Cell>{reagent.log}</Table.Cell>}
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Box position="absolute" x="200" transform="translate(30,30)">
|
||||
<svg background-size="200px" width="200" height="400">
|
||||
<text
|
||||
x="0"
|
||||
y="250"
|
||||
text-anchor="middle"
|
||||
fill="white"
|
||||
font-size="16"
|
||||
transform="translate(0,0) scale(0.8 0.8)"
|
||||
>
|
||||
{/* x axis*/}
|
||||
<tspan x="250" y="318" font-weight="bold" font-size="1.4em">
|
||||
Mass (g)
|
||||
</tspan>
|
||||
<tspan x="0" y="283">
|
||||
{graphLowerRange}
|
||||
</tspan>
|
||||
<tspan x="100" y="283">
|
||||
{round(graphLowerRange + graphIncrement, 1)}
|
||||
</tspan>
|
||||
<tspan x="200" y="283">
|
||||
{round(graphLowerRange + graphIncrement * 2, 1)}
|
||||
</tspan>
|
||||
<tspan x="300" y="283">
|
||||
{round(graphLowerRange + graphIncrement * 3, 1)}
|
||||
</tspan>
|
||||
<tspan x="400" y="283">
|
||||
{round(graphLowerRange + graphIncrement * 4, 1)}
|
||||
</tspan>
|
||||
<tspan x="500" y="283">
|
||||
{graphUpperRange}
|
||||
</tspan>
|
||||
{/* y axis*/}
|
||||
<tspan x="520" y="0" dy="6">
|
||||
{round(maxAbsorbance, 1)}
|
||||
</tspan>
|
||||
<tspan x="520" y="50" dy="6">
|
||||
{round(maxAbsorbance * 0.8, 1)}
|
||||
</tspan>
|
||||
<tspan x="520" y="100" dy="6">
|
||||
{round(maxAbsorbance * 0.6, 1)}
|
||||
</tspan>
|
||||
<tspan x="520" y="150" dy="6">
|
||||
{round(maxAbsorbance * 0.4, 1)}
|
||||
</tspan>
|
||||
<tspan x="520" y="200" dy="6">
|
||||
{round(maxAbsorbance * 0.2, 1)}
|
||||
</tspan>
|
||||
<tspan x="520" y="250" dy="6">
|
||||
0
|
||||
</tspan>
|
||||
</text>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
transform="translate(430,100) rotate(90) scale(0.8 0.8)"
|
||||
fill="white"
|
||||
font-size="16"
|
||||
>
|
||||
<tspan font-weight="bold" font-size="1.4em">
|
||||
Absorbance (AU)
|
||||
</tspan>
|
||||
</text>
|
||||
<g transform="translate(0, 0) scale(0.8 0.8)">
|
||||
{reagentPeaks.map((peak) => (
|
||||
// Triangle peak
|
||||
<polygon
|
||||
key={peak.name}
|
||||
points={`${((peak.mass - 10) / graphUpperRange) * 500},265 ${
|
||||
(peak.mass / graphUpperRange) * 500
|
||||
},${250 - (peak.volume / maxAbsorbance) * 250} ${
|
||||
((peak.mass + 10) / graphUpperRange) * 500
|
||||
},265 `}
|
||||
opacity="0.6"
|
||||
style={{ fill: peak.color }}
|
||||
/>
|
||||
))}
|
||||
<polygon
|
||||
points={`${(lowerRange / deltaRange) * 500},265 ${
|
||||
(lowerRange / deltaRange) * 500
|
||||
},0 ${(upperRange / deltaRange) * 500},0 ${
|
||||
(upperRange / deltaRange) * 500
|
||||
},265`}
|
||||
opacity="0.2"
|
||||
style={{ fill: 'blue' }}
|
||||
/>
|
||||
<line
|
||||
x1={0}
|
||||
y1={265}
|
||||
x2={502}
|
||||
y2={264}
|
||||
stroke={'white'}
|
||||
stroke-width={3}
|
||||
/>
|
||||
<line
|
||||
x1={501}
|
||||
y1={264}
|
||||
x2={501}
|
||||
y2={0}
|
||||
stroke={'white'}
|
||||
stroke-width={3}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</Box>
|
||||
<Box>
|
||||
<Slider
|
||||
name={'Left slider'}
|
||||
position="relative"
|
||||
step={graphUpperRange / 400}
|
||||
height={17.2}
|
||||
format={(value) => round(value)}
|
||||
width={(centerValue / graphUpperRange) * 400 + 'px'}
|
||||
value={lowerRange}
|
||||
minValue={graphLowerRange}
|
||||
maxValue={centerValue}
|
||||
color={'invisible'}
|
||||
onDrag={(e, value) =>
|
||||
act('leftSlider', {
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{' '}
|
||||
</Slider>
|
||||
<Slider
|
||||
name={'Right slider'}
|
||||
position="absolute"
|
||||
height={17.2}
|
||||
format={(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,
|
||||
})
|
||||
}
|
||||
>
|
||||
{' '}
|
||||
</Slider>
|
||||
<Box>
|
||||
<Slider
|
||||
name={'Center slider'}
|
||||
position="relative"
|
||||
step={graphUpperRange / 400}
|
||||
mt={0.3}
|
||||
mb={5}
|
||||
value={centerValue}
|
||||
height={1.9}
|
||||
format={(value) => round(value)}
|
||||
width={400 + 'px'}
|
||||
minValue={graphLowerRange + 1}
|
||||
maxValue={graphUpperRange - 1}
|
||||
color={'invisible'}
|
||||
onDrag={(e, value) =>
|
||||
act('centerSlider', {
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{' '}
|
||||
</Slider>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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<Data>();
|
||||
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 (
|
||||
<Window width={1050} height={650}>
|
||||
<Window.Content scrollable>
|
||||
{!!processing && (
|
||||
<Dimmer fontSize="32px">
|
||||
<Icon name="cog" spin={1} />
|
||||
{' Purifying... ' + round(eta, 0) + 's'}
|
||||
</Dimmer>
|
||||
)}
|
||||
<Section
|
||||
title="Mass Spectroscopy"
|
||||
buttons={
|
||||
<Button
|
||||
icon="power-off"
|
||||
disabled={
|
||||
!!processing || eta <= 0 || !beaker_1_has_contents || !beaker2
|
||||
}
|
||||
tooltip={
|
||||
!beaker_1_has_contents
|
||||
? 'Missing input reagents!'
|
||||
: !beaker2
|
||||
? 'Missing an output beaker!'
|
||||
: eta <= 0
|
||||
? 'No work to be done'
|
||||
: 'Begin purifying'
|
||||
}
|
||||
tooltipPosition="left"
|
||||
onClick={() => act('activate')}
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
{(beaker_1_has_contents && (
|
||||
<MassSpectroscopy
|
||||
lowerRange={lowerRange}
|
||||
centerValue={centerValue}
|
||||
upperRange={upperRange}
|
||||
graphUpperRange={graphUpperRange}
|
||||
maxAbsorbance={peakHeight}
|
||||
reagentPeaks={beaker1.contents}
|
||||
/>
|
||||
)) || <Box>Please insert an input beaker with reagents!</Box>}
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Input beaker"
|
||||
buttons={
|
||||
!!beaker1 && (
|
||||
<>
|
||||
{
|
||||
<Box inline color="label" mr={2}>
|
||||
{beaker1.currentVolume} / {beaker1.maxVolume} units
|
||||
</Box>
|
||||
}
|
||||
<Button icon="eject" onClick={() => act('eject1')}>
|
||||
Eject
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<BeakerMassProfile
|
||||
lowerRange={lowerRange}
|
||||
upperRange={upperRange}
|
||||
beaker={beaker1}
|
||||
/>
|
||||
{!!beaker_1_has_contents && (
|
||||
<Box>{'Eta of selection: ' + round(eta, 0) + ' seconds'}</Box>
|
||||
)}
|
||||
</Section>
|
||||
<Section
|
||||
title="Output beaker"
|
||||
buttons={
|
||||
!!beaker2 && (
|
||||
<>
|
||||
{
|
||||
<Box inline color="label" mr={2}>
|
||||
{beaker2.currentVolume} / {beaker2.maxVolume} units
|
||||
</Box>
|
||||
}
|
||||
<Button icon="eject" onClick={() => act('eject2')}>
|
||||
Eject
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<BeakerMassProfile
|
||||
lowerRange={lowerRange}
|
||||
upperRange={upperRange}
|
||||
beaker={beaker2}
|
||||
/>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
type ProfileProps = {
|
||||
lowerRange: number;
|
||||
upperRange: number;
|
||||
beaker: Beaker;
|
||||
};
|
||||
|
||||
const BeakerMassProfile = (props: ProfileProps) => {
|
||||
const { lowerRange, upperRange, beaker } = props;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{(!beaker && <Box color="label">No beaker loaded.</Box>) ||
|
||||
(beaker.contents.length === 0 && (
|
||||
<Box color="label">Beaker is empty.</Box>
|
||||
)) || (
|
||||
<Table className="candystripe">
|
||||
<Table.Row>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Reagent
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Mass
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Volume
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Purity
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Type
|
||||
</Table.Cell>
|
||||
<Table.Cell bold collapsing color="label">
|
||||
Status
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{beaker.contents.map((reagent) => {
|
||||
const selected =
|
||||
reagent.mass >= lowerRange && reagent.mass <= upperRange;
|
||||
const color = reagent.type === 'Inverted' ? '#b60046' : '#3cf096';
|
||||
|
||||
return (
|
||||
<Table.Row key={reagent.name}>
|
||||
<Table.Cell collapsing color={selected ? 'green' : 'default'}>
|
||||
{reagent.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing color={selected ? 'green' : 'default'}>
|
||||
{reagent.mass}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing color={selected ? 'green' : 'default'}>
|
||||
{reagent.volume}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing color={selected ? 'green' : 'default'}>
|
||||
{`${reagent.purity}%`}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing color={color}>
|
||||
▮{reagent.type}
|
||||
</Table.Cell>
|
||||
{<Table.Cell>{reagent.log}</Table.Cell>}
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<Box
|
||||
style={{
|
||||
width: `${GRAPH_MAX_WIDTH}px`,
|
||||
height: `${GRAPH_MAX_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: `${GRAPH_MAX_WIDTH}px`,
|
||||
height: `${GRAPH_MAX_HEIGHT}px`,
|
||||
top: '10px',
|
||||
}}
|
||||
>
|
||||
{/* x axis*/}
|
||||
<text
|
||||
text-anchor="middle"
|
||||
fill="white"
|
||||
transform={`scale(${x_scale} 1)`}
|
||||
font-size="14"
|
||||
>
|
||||
<tspan
|
||||
x="40%"
|
||||
y={`${base_line + 40}px`}
|
||||
font-weight="bold"
|
||||
font-size="16"
|
||||
>
|
||||
Mass (G)
|
||||
</tspan>
|
||||
<tspan x="0%" y={`${base_line + 20}px`}>
|
||||
{graphLowerRange}
|
||||
</tspan>
|
||||
<tspan x="20%" y={`${base_line + 20}px`}>
|
||||
{round(graphLowerRange + graphIncrement, 1)}
|
||||
</tspan>
|
||||
<tspan x="40%" y={`${base_line + 20}px`}>
|
||||
{round(graphLowerRange + graphIncrement * 2, 1)}
|
||||
</tspan>
|
||||
<tspan x="60%" y={`${base_line + 20}px`}>
|
||||
{round(graphLowerRange + graphIncrement * 3, 1)}
|
||||
</tspan>
|
||||
<tspan x="80%" y={`${base_line + 20}px`}>
|
||||
{round(graphLowerRange + graphIncrement * 4, 1)}
|
||||
</tspan>
|
||||
<tspan x="100%" y={`${base_line + 20}px`}>
|
||||
{graphUpperRange}
|
||||
</tspan>
|
||||
</text>
|
||||
<line
|
||||
x1={0}
|
||||
y1={base_line}
|
||||
x2={base_width}
|
||||
y2={base_line}
|
||||
stroke={'white'}
|
||||
stroke-width={3}
|
||||
/>
|
||||
|
||||
{/* y axis*/}
|
||||
<text
|
||||
text-anchor="middle"
|
||||
fill="white"
|
||||
transform={`scale(1 ${y_scale})`}
|
||||
font-size="14"
|
||||
>
|
||||
<tspan x={`${base_width + 20}px`} y="100%">
|
||||
0
|
||||
</tspan>
|
||||
<tspan x={`${base_width + 20}px`} y="80%">
|
||||
{round(maxAbsorbance * 0.2, 1)}
|
||||
</tspan>
|
||||
<tspan x={`${base_width + 20}px`} y="60%">
|
||||
{round(maxAbsorbance * 0.4, 1)}
|
||||
</tspan>
|
||||
<tspan x={`${base_width + 20}px`} y="40%">
|
||||
{round(maxAbsorbance * 0.6, 1)}
|
||||
</tspan>
|
||||
<tspan x={`${base_width + 20}px`} y="20%">
|
||||
{round(maxAbsorbance * 0.8, 1)}
|
||||
</tspan>
|
||||
<tspan x={`${base_width + 20}px`} y="0%">
|
||||
{round(maxAbsorbance, 1)}
|
||||
</tspan>
|
||||
</text>
|
||||
<text
|
||||
text-anchor="middle"
|
||||
transform={`translate(${base_width + 35},${
|
||||
GRAPH_MAX_HEIGHT * 0.4
|
||||
}) rotate(90) scale(1, 1.2)`}
|
||||
fill="white"
|
||||
font-size="17"
|
||||
font-weight="bold"
|
||||
>
|
||||
<tspan>Absorbance (AU)</tspan>
|
||||
</text>
|
||||
<line
|
||||
x1={base_width}
|
||||
y1={base_line}
|
||||
x2={base_width}
|
||||
y2={0}
|
||||
stroke={'white'}
|
||||
stroke-width={3}
|
||||
/>
|
||||
|
||||
{/* Graph */}
|
||||
<g transform={`scale(${x_scale} ${y_scale})`}>
|
||||
{reagentPeaks.map((peak) => (
|
||||
<>
|
||||
{/* Triangle peak */}
|
||||
<polygon
|
||||
key={peak.name}
|
||||
points={`${
|
||||
((peak.mass - 5) / graphUpperRange) * GRAPH_MAX_WIDTH
|
||||
},${GRAPH_MAX_HEIGHT}
|
||||
${(peak.mass / graphUpperRange) * GRAPH_MAX_WIDTH},${
|
||||
GRAPH_MAX_HEIGHT -
|
||||
(peak.volume / maxAbsorbance) * GRAPH_MAX_HEIGHT
|
||||
}
|
||||
${
|
||||
((peak.mass + 5) / graphUpperRange) * GRAPH_MAX_WIDTH
|
||||
}, ${GRAPH_MAX_HEIGHT}`}
|
||||
opacity="0.6"
|
||||
style={{
|
||||
fill: peak.type === 'Inverted' ? '#b60046' : '#3cf096',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Background */}
|
||||
<polygon
|
||||
points={`${
|
||||
(lowerRange / deltaRange) * GRAPH_MAX_WIDTH
|
||||
},${GRAPH_MAX_HEIGHT} ${
|
||||
(lowerRange / deltaRange) * GRAPH_MAX_WIDTH
|
||||
},0 ${(upperRange / deltaRange) * GRAPH_MAX_WIDTH},0 ${
|
||||
(upperRange / deltaRange) * GRAPH_MAX_WIDTH
|
||||
},${GRAPH_MAX_HEIGHT}`}
|
||||
opacity="0.1"
|
||||
style={{ fill: 'blue' }}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
{/* Sliders */}
|
||||
<Slider
|
||||
name={'Left slider'}
|
||||
step={graphUpperRange / base_width}
|
||||
suppressFlicker
|
||||
height={17.2}
|
||||
format={(value: number) => round(value, 2)}
|
||||
width={(centerValue / graphUpperRange) * base_width + 'px'}
|
||||
value={lowerRange}
|
||||
minValue={graphLowerRange}
|
||||
maxValue={centerValue}
|
||||
color={'invisible'}
|
||||
onDrag={(e, value) =>
|
||||
act('leftSlider', {
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Slider
|
||||
name={'Right slider'}
|
||||
height={17.2}
|
||||
suppressFlicker
|
||||
format={(value: number) => 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,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Slider
|
||||
name={'Center slider'}
|
||||
step={graphUpperRange / base_width}
|
||||
suppressFlicker
|
||||
mt={1.2}
|
||||
value={centerValue}
|
||||
height={1.9}
|
||||
format={(value: number) => round(value, 2)}
|
||||
width={base_width + 'px'}
|
||||
minValue={graphLowerRange + 1}
|
||||
maxValue={graphUpperRange - 1}
|
||||
color={'invisible'}
|
||||
onDrag={(e, value) =>
|
||||
act('centerSlider', {
|
||||
value: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user