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 ( - <> - - - - {/* x axis*/} - - Mass (g) - - - {graphLowerRange} - - - {round(graphLowerRange + graphIncrement, 1)} - - - {round(graphLowerRange + graphIncrement * 2, 1)} - - - {round(graphLowerRange + graphIncrement * 3, 1)} - - - {round(graphLowerRange + graphIncrement * 4, 1)} - - - {graphUpperRange} - - {/* y axis*/} - - {round(maxAbsorbance, 1)} - - - {round(maxAbsorbance * 0.8, 1)} - - - {round(maxAbsorbance * 0.6, 1)} - - - {round(maxAbsorbance * 0.4, 1)} - - - {round(maxAbsorbance * 0.2, 1)} - - - 0 - - - - - Absorbance (AU) - - - - {reagentPeaks.map((peak) => ( - // Triangle peak - - ))} - - - - - - - - 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 ( + + + {/* x axis*/} + + + Mass (G) + + + {graphLowerRange} + + + {round(graphLowerRange + graphIncrement, 1)} + + + {round(graphLowerRange + graphIncrement * 2, 1)} + + + {round(graphLowerRange + graphIncrement * 3, 1)} + + + {round(graphLowerRange + graphIncrement * 4, 1)} + + + {graphUpperRange} + + + + + {/* y axis*/} + + + 0 + + + {round(maxAbsorbance * 0.2, 1)} + + + {round(maxAbsorbance * 0.4, 1)} + + + {round(maxAbsorbance * 0.6, 1)} + + + {round(maxAbsorbance * 0.8, 1)} + + + {round(maxAbsorbance, 1)} + + + + Absorbance (AU) + + + + {/* Graph */} + + {reagentPeaks.map((peak) => ( + <> + {/* Triangle peak */} + + + {/* Background */} + + + ))} + + + + {/* 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, + }) + } + /> + + ); +};