diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
index ab3d53adacf..8cc2105052f 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm
@@ -72,50 +72,37 @@
max_integrity = 350
armor_type = /datum/armor/unary_cryo_cell
layer = MOB_LAYER - 0.2 //SKYRAT EDIT - Fixing the opacity of cryo cells - ORIGINAL: layer = MOB_LAYER
- state_open = FALSE
circuit = /obj/item/circuitboard/machine/cryo_tube
occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal)
processing_flags = NONE
-
+ fair_market_price = 10
+ payment_department = ACCOUNT_MED
use_power = IDLE_POWER_USE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.75
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 1.5
flags_1 = PREVENT_CLICK_UNDER_1
+ ///If TRUE will eject the mob once healing is complete
var/autoeject = TRUE
- var/volume = 100
-
+ ///Increased via upgraded parts, higher values will provide better healing and use smaller cryoxodane
var/efficiency = 1
- var/sleep_factor = 0.00125
- var/unconscious_factor = 0.001
- /// Our approximation of a mob's heat capacity.
+ ///Our approximation of a mob's heat capacity. Higher tier parts will provide better cooling for mobs
var/heat_capacity = 20000
+ ///Works with heat capacity and is increased with higher tier parts. How quickly the mobs temperature changes in the chamber
var/conduction_coefficient = 0.3
-
+ ///The beaker usually contains cryoxadone that is pumped into the mob
var/obj/item/reagent_containers/cup/beaker = null
- var/consume_gas = FALSE
-
+ ///Inform medical about cryo status over this radio
var/obj/item/radio/radio
- var/radio_key = /obj/item/encryptionkey/headset_med
- var/radio_channel = RADIO_CHANNEL_MEDICAL
-
/// Visual content - Occupant
var/atom/movable/visual/cryo_occupant/occupant_vis
-
- var/message_cooldown
///Cryo will continue to treat people with 0 damage but existing wounds, but will sound off when damage healing is done in case doctors want to directly treat the wounds instead
var/treating_wounds = FALSE
- /// Cryo should notify doctors if the patient is dead, and eject them if autoeject is enabled
- var/patient_dead = FALSE
- fair_market_price = 10
- payment_department = ACCOUNT_MED
-
/// Reference to the datum connector we're using to interface with the pipe network
var/datum/gas_machine_connector/internal_connector
/// Check if the machine has been turned on
var/on = FALSE
-
/datum/armor/unary_cryo_cell
energy = 100
fire = 30
@@ -123,75 +110,39 @@
/obj/machinery/cryo_cell/Initialize(mapload)
. = ..()
- if(is_operational)
- begin_processing()
radio = new(src)
- radio.keyslot = new radio_key
+ radio.keyslot = new /obj/item/encryptionkey/headset_med
radio.subspace_transmission = TRUE
radio.canhear_range = 0
radio.recalculateChannels()
- occupant_vis = new(null, src)
+ occupant_vis = new(mapload, src)
vis_contents += occupant_vis
- register_context()
internal_connector = new(loc, src, dir, CELL_VOLUME * 0.5)
-/obj/machinery/cryo_cell/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
- . = ..()
- if(same_z_layer)
- return
- SET_PLANE(occupant_vis, PLANE_TO_TRUE(occupant_vis.plane), new_turf)
-
-/obj/machinery/cryo_cell/set_occupant(atom/movable/new_occupant)
- . = ..()
- update_appearance()
-
-/obj/machinery/cryo_cell/on_construction(mob/user)
- ..(user, dir, dir)
-
-/obj/machinery/cryo_cell/RefreshParts()
- . = ..()
- var/C
- for(var/datum/stock_part/matter_bin/M in component_parts)
- C += M.tier
-
- efficiency = initial(efficiency) * C
- sleep_factor = initial(sleep_factor) * C
- unconscious_factor = initial(unconscious_factor) * C
- heat_capacity = initial(heat_capacity) / C
- conduction_coefficient = initial(conduction_coefficient) * C
-
-/obj/machinery/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results"
- . = ..()
- if(in_range(user, src) || isobserver(user))
- . += span_notice("The status display reads: Efficiency at [efficiency*100]%.")
-
-/obj/machinery/cryo_cell/add_context(atom/source, list/context, obj/item/held_item, mob/user)
- . = ..()
- context[SCREENTIP_CONTEXT_CTRL_LMB] = "Turn [on ? "off" : "on"]"
- context[SCREENTIP_CONTEXT_ALT_LMB] = "[state_open ? "Close" : "Open"] door"
- if(!held_item)
- return CONTEXTUAL_SCREENTIP_SET
- switch(held_item.tool_behaviour)
- if(TOOL_SCREWDRIVER)
- context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel"
- if(TOOL_WRENCH)
- context[SCREENTIP_CONTEXT_LMB] = "Rotate"
- return CONTEXTUAL_SCREENTIP_SET
-
+ register_context()
/obj/machinery/cryo_cell/Destroy()
- vis_contents.Cut()
+ on = FALSE
+ vis_contents.Cut()
QDEL_NULL(occupant_vis)
QDEL_NULL(radio)
QDEL_NULL(beaker)
-
QDEL_NULL(internal_connector)
return ..()
+/obj/machinery/cryo_cell/on_deconstruction()
+ if(occupant)
+ occupant.vis_flags &= ~VIS_INHERIT_PLANE
+ REMOVE_TRAIT(occupant, TRAIT_IMMOBILIZED, CRYO_TRAIT)
+ REMOVE_TRAIT(occupant, TRAIT_FORCED_STANDING, CRYO_TRAIT)
+
+ if(beaker)
+ beaker.forceMove(drop_location())
+
/obj/machinery/cryo_cell/contents_explosion(severity, target)
. = ..()
if(!beaker)
@@ -205,28 +156,81 @@
if(EXPLODE_LIGHT)
SSexplosions.low_mov_atom += beaker
-/obj/machinery/cryo_cell/Exited(atom/movable/gone, direction)
+/obj/machinery/cryo_cell/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents)
. = ..()
- if(gone == beaker)
- beaker = null
+ if(same_z_layer)
+ return
+ SET_PLANE(occupant_vis, PLANE_TO_TRUE(occupant_vis.plane), new_turf)
-/obj/machinery/cryo_cell/on_deconstruction()
- if(occupant)
- occupant.vis_flags &= ~VIS_INHERIT_PLANE
- REMOVE_TRAIT(occupant, TRAIT_IMMOBILIZED, CRYO_TRAIT)
- REMOVE_TRAIT(occupant, TRAIT_FORCED_STANDING, CRYO_TRAIT)
+/obj/machinery/cryo_cell/set_occupant(atom/movable/new_occupant)
+ . = ..()
+ update_appearance()
- if(beaker)
- beaker.forceMove(drop_location())
- beaker = null
+/obj/machinery/cryo_cell/RefreshParts()
+ . = ..()
+ var/C
+ for(var/datum/stock_part/matter_bin/M in component_parts)
+ C += M.tier
-/obj/machinery/cryo_cell/update_icon_state()
- icon_state = (state_open) ? "pod-open" : ((on && is_operational) ? "pod-on" : "pod-off")
- return ..()
+ efficiency = initial(efficiency) * C
+ heat_capacity = initial(heat_capacity) / C
+ conduction_coefficient = initial(conduction_coefficient) * C
+
+/obj/machinery/cryo_cell/examine(mob/user) //this is leaving out everything but efficiency since they follow the same idea of "better beaker, better results"
+ . = ..()
+ if(in_range(user, src) || isobserver(user))
+ . += span_notice("The status display reads: Efficiency at [efficiency * 100]%.")
+ if(occupant)
+ if(on)
+ . += span_notice("Someone's inside [src]!")
+ else
+ . += span_notice("You can barely make out a form floating in [src].")
+ else
+ . += span_notice("[src] seems empty.")
+ if(beaker)
+ . += span_notice("A beaker of [beaker.reagents.maximum_volume]u capacity is located inside.")
+ else
+ . += span_warning("Its missing a beaker.")
+
+ . += span_notice("Use [EXAMINE_HINT("Alt-Click")] to [state_open ? "Close" : "Open"] the machine.")
+ . += span_notice("Use [EXAMINE_HINT("Ctrl-Click")] to turn [on ? "Off" : "On"] the machine.")
+
+ . += span_notice("Its maintainence panel can be [EXAMINE_HINT("screwed")] open.")
+ if(panel_open)
+ . += span_notice("[src] can be [EXAMINE_HINT("pried")] apart.")
+ . += span_notice("[src] can be rotated with a [EXAMINE_HINT("wrench")].")
+ else if(machine_stat & NOPOWER)
+ . += span_notice("[src] can be [EXAMINE_HINT("pried")] open.")
+
+/obj/machinery/cryo_cell/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ context[SCREENTIP_CONTEXT_CTRL_LMB] = "Turn [on ? "off" : "on"]"
+ context[SCREENTIP_CONTEXT_ALT_LMB] = "[state_open ? "Close" : "Open"] door"
+ if(isnull(held_item))
+ return CONTEXTUAL_SCREENTIP_SET
+
+ if(QDELETED(beaker) && istype(held_item, /obj/item/reagent_containers/cup))
+ context[SCREENTIP_CONTEXT_LMB] = "Insert beaker"
+ return CONTEXTUAL_SCREENTIP_SET
+
+ switch(held_item.tool_behaviour)
+ if(TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel"
+ if(TOOL_CROWBAR)
+ if(!state_open && !panel_open && !is_operational)
+ context[SCREENTIP_CONTEXT_LMB] = "Pry Open"
+ else if(panel_open)
+ context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
+ if(TOOL_WRENCH)
+ context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Rotate" : ""]"
+ return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/cryo_cell/update_icon()
- . = ..()
SET_PLANE_IMPLICIT(src, initial(plane))
+ return ..()
+
+/obj/machinery/cryo_cell/update_icon_state()
+ icon_state = state_open ? "pod-open" : ((on && is_operational) ? "pod-on" : "pod-off")
+ return ..()
/obj/machinery/cryo_cell/update_overlays()
. = ..()
@@ -234,117 +238,133 @@
. += "pod-panel"
if(state_open)
return
- if(on && is_operational)
- . += mutable_appearance('icons/obj/medical/cryogenics.dmi', "cover-on", ABOVE_ALL_MOB_LAYER, src, plane = ABOVE_GAME_PLANE)
- else
- . += mutable_appearance('icons/obj/medical/cryogenics.dmi', "cover-off", ABOVE_ALL_MOB_LAYER, src, plane = ABOVE_GAME_PLANE)
+ . += mutable_appearance('icons/obj/medical/cryogenics.dmi', "cover-[on && is_operational ? "on" : "off"]", ABOVE_ALL_MOB_LAYER, src, plane = ABOVE_GAME_PLANE)
-/obj/machinery/cryo_cell/nap_violation(mob/violator)
- open_machine()
+/obj/machinery/cryo_cell/dump_inventory_contents(list/subset = list(occupant))
+ //only drop the mob and nothing else by default when opening the machine
+ return ..(subset)
+/obj/machinery/cryo_cell/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == beaker)
+ beaker = null
+/**
+ * Turns the machine on/off
+ *
+ * Arguments
+ * * active - TRUE to turn the machine on, FALSE to turn it off
+ */
/obj/machinery/cryo_cell/proc/set_on(active)
+ PRIVATE_PROC(TRUE)
+
if(on == active)
return
+
SEND_SIGNAL(src, COMSIG_CRYO_SET_ON, active)
. = on
on = active
- if(on)
- update_use_power(ACTIVE_POWER_USE)
- else
- update_use_power(IDLE_POWER_USE)
update_appearance()
-/obj/machinery/cryo_cell/on_set_is_operational(old_value)
- if(old_value) //Turned off
- set_on(FALSE)
- end_processing()
- else //Turned on
+ update_use_power(on ? ACTIVE_POWER_USE : IDLE_POWER_USE)
+ if(on) //Turned on
begin_processing()
+ else //Turned off
+ end_processing()
+/obj/machinery/cryo_cell/begin_processing()
+ . = ..()
+ SSair.start_processing_machine(src)
+
+/obj/machinery/cryo_cell/end_processing()
+ . = ..()
+ SSair.stop_processing_machine(src)
+
+/obj/machinery/cryo_cell/on_set_is_operational(old_value)
+ //Turned off
+ if(old_value)
+ set_on(FALSE)
/obj/machinery/cryo_cell/process(seconds_per_tick)
- ..()
- if(!occupant)
- return
+ if(!on || QDELETED(occupant))
+ //somehow an deleting mob is inside us. dump everything out
+ if(!isnull(occupant) && QDELING(occupant))
+ open_machine()
+ on = FALSE //in case panel was open we need to set to FALSE explicitly
- if(!on)
- // Should turn on if set to auto
- if(autoeject)
- set_on(TRUE)
- else
- return
+ //if not on end processing
+ if(!on)
+ set_on(FALSE) //this explicitly disables processing so is nessassary
+ . = PROCESS_KILL
+
+ return
var/mob/living/mob_occupant = occupant
if(mob_occupant.on_fire)
mob_occupant.extinguish_mob()
- if(!check_nap_violations())
- return
if(mob_occupant.stat == DEAD) // Notify doctors and potentially eject if the patient is dead
set_on(FALSE)
var/msg = "Patient is deceased."
if(autoeject) // Eject if configured.
msg += " Auto ejecting patient now."
open_machine()
- // Only need to tell them once
- if(!patient_dead)
- playsound(src, 'sound/machines/cryo_warning.ogg', volume)
- patient_dead = TRUE
- radio.talk_into(src, msg, radio_channel)
- return
+ playsound(src, 'sound/machines/cryo_warning.ogg', 100)
+ radio.talk_into(src, msg, RADIO_CHANNEL_MEDICAL)
+ return PROCESS_KILL
- patient_dead = FALSE
-
- if(mob_occupant.get_organic_health() >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people.
+ // Don't bother with fully healed people.
+ if(mob_occupant.get_organic_health() >= mob_occupant.getMaxHealth())
if(iscarbon(mob_occupant))
var/mob/living/carbon/C = mob_occupant
if(C.all_wounds)
if(!treating_wounds) // if we have wounds and haven't already alerted the doctors we're only dealing with the wounds, let them know
treating_wounds = TRUE
- playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors.
- var/msg = "Patient vitals fully recovered, continuing automated wound treatment."
- radio.talk_into(src, msg, radio_channel)
+ playsound(src, 'sound/machines/cryo_warning.ogg', 100) // Bug the doctors.
+ radio.talk_into(src, "Patient vitals fully recovered, continuing automated wound treatment.", RADIO_CHANNEL_MEDICAL)
else // otherwise if we were only treating wounds and now we don't have any, turn off treating_wounds so we can boot 'em out
treating_wounds = FALSE
if(!treating_wounds)
set_on(FALSE)
- playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors.
+ playsound(src, 'sound/machines/cryo_warning.ogg', 100) // Bug the doctors.
var/msg = "Patient fully restored."
if(autoeject) // Eject if configured.
msg += " Auto ejecting patient now."
open_machine()
- radio.talk_into(src, msg, radio_channel)
- return
+ radio.talk_into(src, msg, RADIO_CHANNEL_MEDICAL)
+ return PROCESS_KILL
var/datum/gas_mixture/air1 = internal_connector.gas_connector.airs[1]
-
- if(air1.total_moles() > CRYO_MIN_GAS_MOLES)
- if(beaker)
- beaker.reagents.trans_to(
- occupant,
- (CRYO_TX_QTY / (efficiency * CRYO_MULTIPLY_FACTOR)) * seconds_per_tick,
- efficiency * CRYO_MULTIPLY_FACTOR,
- methods = VAPOR
- )
- consume_gas = TRUE
- return TRUE
+ if(!QDELETED(beaker) && air1.total_moles() > CRYO_MIN_GAS_MOLES)
+ beaker.reagents.trans_to(
+ occupant,
+ (CRYO_TX_QTY / (efficiency * CRYO_MULTIPLY_FACTOR)) * seconds_per_tick,
+ efficiency * CRYO_MULTIPLY_FACTOR,
+ methods = VAPOR
+ )
/obj/machinery/cryo_cell/process_atmos()
- ..()
-
if(!on)
- return
+ return PROCESS_KILL
var/datum/gas_mixture/air1 = internal_connector.gas_connector.airs[1]
- if(!internal_connector.gas_connector.nodes[1] || !internal_connector.gas_connector.airs[1] || !air1.gases.len || air1.total_moles() < CRYO_MIN_GAS_MOLES) // Turn off if the machine won't work.
- var/msg = "Insufficient cryogenic gas, shutting down."
- radio.talk_into(src, msg, radio_channel)
+ //check for workable conditions
+ if(!internal_connector.gas_connector.nodes[1] || !air1 || !air1.gases.len || air1.total_moles() < CRYO_MIN_GAS_MOLES) // Turn off if the machine won't work.
set_on(FALSE)
- return
+ var/msg = "Insufficient cryogenic gas, shutting down."
+ if(autoeject) // Eject if configured.
+ msg += " Auto ejecting patient now."
+ open_machine()
+ radio.talk_into(src, msg, RADIO_CHANNEL_MEDICAL)
+ return PROCESS_KILL
- if(occupant)
+ //take damage from high temperatures
+ if(air1.temperature > 2000)
+ take_damage(clamp((air1.temperature) / 200, 10, 20), BURN)
+
+ //adjust temperature of mob
+ if(!QDELETED(occupant))
var/mob/living/mob_occupant = occupant
var/cold_protection = 0
var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant.
@@ -355,7 +375,6 @@
if(abs(temperature_delta) > 1)
var/air_heat_capacity = air1.heat_capacity()
-
var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * CALCULATE_CONDUCTION_ENERGY(temperature_delta, heat_capacity, air_heat_capacity)
mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB)
@@ -366,48 +385,42 @@
var/mob/living/carbon/human/humi = mob_occupant
humi.adjust_coretemperature(humi.bodytemperature - humi.coretemperature)
-
- air1.garbage_collect()
-
- if(air1.temperature > 2000)
- take_damage(clamp((air1.temperature)/200, 10, 20), BURN)
-
- internal_connector.gas_connector.update_parents()
+ //spread temperature changes throughout the pipenet
+ internal_connector.gas_connector.update_parents()
/obj/machinery/cryo_cell/handle_internal_lifeform(mob/lifeform_inside_me, breath_request)
-
if(breath_request <= 0)
return null
+
+ //return breathable air
var/datum/gas_mixture/air1 = internal_connector.gas_connector.airs[1]
var/breath_percentage = breath_request / air1.volume
- return air1.remove(air1.total_moles() * breath_percentage)
+ . = air1.remove(air1.total_moles() * breath_percentage)
+
+ //update molar changes throughout the pipenet
+ internal_connector.gas_connector.update_parents()
/obj/machinery/cryo_cell/assume_air(datum/gas_mixture/giver)
internal_connector.gas_connector.airs[1].merge(giver)
-/obj/machinery/cryo_cell/relaymove(mob/living/user, direction)
- if(message_cooldown <= world.time)
- message_cooldown = world.time + 50
- to_chat(user, span_warning("[src]'s door won't budge!"))
+/obj/machinery/cryo_cell/return_temperature()
+ var/datum/gas_mixture/internal_air = internal_connector.gas_connector.airs[1]
-/obj/machinery/cryo_cell/open_machine(drop = FALSE, density_to_set = FALSE)
+ return internal_air.total_moles() > CRYO_MIN_GAS_MOLES ? internal_air.temperature : ..()
+
+/obj/machinery/cryo_cell/open_machine(drop = TRUE, density_to_set = FALSE)
if(!state_open && !panel_open)
set_on(FALSE)
- for(var/mob/M in contents) //only drop mobs
- M.forceMove(get_turf(src))
- set_occupant(null)
flick("pod-open-anim", src)
return ..()
/obj/machinery/cryo_cell/close_machine(mob/living/carbon/user, density_to_set = TRUE)
treating_wounds = FALSE
- if((isnull(user) || istype(user)) && state_open && !panel_open)
- if(loc == user?.loc)
- to_chat(user, span_warning("You can't close [src] on yourself!"))
- return
+ if(state_open && !panel_open)
flick("pod-close-anim", src)
- ..(user)
- return occupant
+ . = ..()
+ if(!QDELETED(occupant)) //auto on if an occupant is inside
+ set_on(TRUE)
/obj/machinery/cryo_cell/container_resist_act(mob/living/user)
user.changeNext_move(CLICK_CD_BREAKOUT)
@@ -422,16 +435,6 @@
span_notice("You successfully break out of [src]!"))
open_machine()
-/obj/machinery/cryo_cell/examine(mob/user)
- . = ..()
- if(occupant)
- if(on)
- . += "Someone's inside [src]!"
- else
- . += "You can barely make out a form floating in [src]."
- else
- . += "[src] seems empty."
-
/obj/machinery/cryo_cell/MouseDrop_T(mob/target, mob/user)
if(user.incapacitated() || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !ISADVANCEDTOOLUSER(user))
return
@@ -445,25 +448,32 @@
close_machine(target)
/obj/machinery/cryo_cell/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(on)
+ balloon_alert(user, "turn off!")
+ return
+ if(occupant)
+ balloon_alert(user, "occupant inside!")
+ return
- if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", tool)))
+ if(default_deconstruction_screwdriver(user, "pod-off", "pod-off", tool))
update_appearance()
- else
- to_chat(user, "You can't access the maintenance panel while the pod is " \
- + (on ? "active" : (occupant ? "full" : "open")) + "!")
- return ITEM_INTERACT_SUCCESS
-
-/obj/machinery/cryo_cell/crowbar_act(mob/living/user, obj/item/tool)
- if(on || state_open)
- return FALSE
- if(!panel_open)
- balloon_alert(user, "open panel!")
return ITEM_INTERACT_SUCCESS
- var/unsafe_wrenching = FALSE
- var/filled_pipe = FALSE
- var/datum/gas_mixture/environment_air = loc.return_air()
- var/datum/gas_mixture/inside_air = internal_connector.gas_connector.airs[1]
+/obj/machinery/cryo_cell/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(on)
+ balloon_alert(user, "turn off!")
+ return
+
+ var/can_crowbar = FALSE
+ if(!state_open && !panel_open && !is_operational) //can pry open
+ can_crowbar = TRUE
+ else if(panel_open) //can deconstruct
+ can_crowbar = TRUE
+ if(!can_crowbar)
+ return
+
var/obj/machinery/atmospherics/node = internal_connector.gas_connector.nodes[1]
var/internal_pressure = 0
@@ -471,165 +481,180 @@
var/obj/machinery/atmospherics/components/unary/portables_connector/portable_devices_connector = node
internal_pressure = !portable_devices_connector.connected_device ? 1 : 0
+ var/datum/gas_mixture/inside_air = internal_connector.gas_connector.airs[1]
if(inside_air.total_moles() > 0)
- filled_pipe = TRUE
if(!node || internal_pressure > 0)
+ var/datum/gas_mixture/environment_air = loc.return_air()
internal_pressure = inside_air.return_pressure() - environment_air.return_pressure()
- if(!filled_pipe)
- default_deconstruction_crowbar(tool)
- return ITEM_INTERACT_SUCCESS
-
- to_chat(user, span_notice("You begin to unfasten \the [src]..."))
-
+ var/unsafe_release = FALSE
if(internal_pressure > 2 * ONE_ATMOSPHERE)
- to_chat(user, span_warning("As you begin deconstructing \the [src] a gush of air blows in your face... maybe you should reconsider?"))
- unsafe_wrenching = TRUE
+ to_chat(user, span_warning("As you begin prying \the [src] a gush of air blows in your face... maybe you should reconsider?"))
+ if(!do_after(user, 2 SECONDS, target = src))
+ return
+ unsafe_release = TRUE
- if(!do_after(user, 2 SECONDS, src))
- return
- if(unsafe_wrenching)
+ var/deconstruct = FALSE
+ if(!default_pry_open(tool))
+ if(!default_deconstruction_crowbar(tool, custom_deconstruct = TRUE))
+ return
+ else
+ deconstruct = TRUE
+
+ if(unsafe_release)
internal_connector.gas_connector.unsafe_pressure_release(user, internal_pressure)
tool.play_tool_sound(src, 50)
- deconstruct(TRUE)
+ if(deconstruct)
+ deconstruct(TRUE)
return ITEM_INTERACT_SUCCESS
/obj/machinery/cryo_cell/wrench_act(mob/living/user, obj/item/tool)
- if(on || occupant || state_open)
- return FALSE
+ . = ITEM_INTERACT_BLOCKING
+ if(on)
+ balloon_alert(user, "turn off!")
+ return
+ if(occupant)
+ balloon_alert(user, "occupant inside!")
+ return
+ if(state_open)
+ balloon_alert(user, "close first!")
+ return
+
if(default_change_direction_wrench(user, tool))
update_appearance()
return ITEM_INTERACT_SUCCESS
/obj/machinery/cryo_cell/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/reagent_containers/cup))
- . = 1 //no afterattack
+ . = TRUE //no afterattack
if(beaker)
- to_chat(user, span_warning("A beaker is already loaded into [src]!"))
+ balloon_alert(user, "beaker present!")
return
if(!user.transferItemToLoc(I, src))
return
beaker = I
- user.visible_message(span_notice("[user] places [I] in [src]."), \
- span_notice("You place [I] in [src]."))
+ balloon_alert(user, "beaker inserted")
var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list)
user.log_message("added an [I] to cryo containing [reagentlist].", LOG_GAME)
return
+
return ..()
/obj/machinery/cryo_cell/ui_state(mob/user)
return GLOB.notcontained_state
-
/obj/machinery/cryo_cell/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Cryo", name)
ui.open()
+/obj/machinery/cryo_cell/ui_static_data(mob/user)
+ . = list()
+ .["T0C"] = T0C
+
/obj/machinery/cryo_cell/ui_data()
- var/list/data = list()
- data["isOperating"] = on
- data["hasOccupant"] = occupant ? TRUE : FALSE
- data["isOpen"] = state_open
- data["autoEject"] = autoeject
+ . = list()
+ .["isOperating"] = on
+ .["isOpen"] = state_open
+ .["autoEject"] = autoeject
- data["occupant"] = list()
- if(occupant)
+ var/list/occupant_data = null
+ if(!QDELETED(occupant))
var/mob/living/mob_occupant = occupant
- data["occupant"]["name"] = mob_occupant.name
+ occupant_data = list()
+
+ occupant_data["name"] = mob_occupant.name
if(mob_occupant.stat == DEAD)
- data["occupant"]["stat"] = "Dead"
- data["occupant"]["statstate"] = "bad"
+ occupant_data["stat"] = "Dead"
else if (HAS_TRAIT(mob_occupant, TRAIT_KNOCKEDOUT))
- data["occupant"]["stat"] = "Unconscious"
- data["occupant"]["statstate"] = "good"
+ occupant_data["stat"] = "Unconscious"
else
- data["occupant"]["stat"] = "Conscious"
- data["occupant"]["statstate"] = "bad"
+ occupant_data["stat"] = "Conscious"
- data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1)
- if(mob_occupant.bodytemperature < T0C) // Green if the mob can actually be healed by cryoxadone.
- data["occupant"]["temperaturestatus"] = "good"
- else
- data["occupant"]["temperaturestatus"] = "bad"
+ occupant_data["bodyTemperature"] = round(mob_occupant.bodytemperature, 1)
- data["occupant"]["health"] = round(mob_occupant.health, 1)
- data["occupant"]["maxHealth"] = mob_occupant.maxHealth
- data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD
- data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1)
- data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1)
- data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1)
- data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1)
+ occupant_data["health"] = mob_occupant.health
+ occupant_data["maxHealth"] = mob_occupant.maxHealth
+ occupant_data["bruteLoss"] = mob_occupant.getBruteLoss()
+ occupant_data["oxyLoss"] = mob_occupant.getOxyLoss()
+ occupant_data["toxLoss"] = mob_occupant.getToxLoss()
+ occupant_data["fireLoss"] = mob_occupant.getFireLoss()
+ .["occupant"] = occupant_data
var/datum/gas_mixture/air1 = internal_connector.gas_connector.airs[1]
- data["cellTemperature"] = round(air1.temperature, 1)
+ .["cellTemperature"] = air1.temperature
- data["isBeakerLoaded"] = beaker ? TRUE : FALSE
- var/beakerContents = list()
- if(beaker && beaker.reagents && beaker.reagents.reagent_list.len)
- for(var/datum/reagent/R in beaker.reagents.reagent_list)
- beakerContents += list(list("name" = R.name, "volume" = R.volume))
- data["beakerContents"] = beakerContents
- return data
+ var/list/beaker_data = null
+ if(!QDELETED(beaker))
+ beaker_data = list()
+ beaker_data["maxVolume"] = beaker.volume
+ beaker_data["currentVolume"] = round(beaker.reagents.total_volume, 0.01)
+ 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...
+ beaker_data["contents"] = beakerContents
+ .["beaker"] = beaker_data
-/obj/machinery/cryo_cell/ui_act(action, params)
+/obj/machinery/cryo_cell/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
+
switch(action)
if("power")
if(on)
set_on(FALSE)
else if(!state_open)
set_on(TRUE)
- . = TRUE
+ return TRUE
+
if("door")
- if(state_open)
+ if(state_open && get_turf(ui.user) != get_turf(src))
close_machine()
else
open_machine()
- . = TRUE
+ return TRUE
+
if("autoeject")
autoeject = !autoeject
- . = TRUE
- if("ejectbeaker")
- if(beaker)
- beaker.forceMove(drop_location())
- if(Adjacent(usr) && !issilicon(usr))
- usr.put_in_hands(beaker)
- beaker = null
- . = TRUE
+ return TRUE
+
+ if("eject")
+ if(!QDELETED(beaker))
+ var/mob/living/user = ui.user
+ if(Adjacent(user) && !issilicon(user))
+ user.put_in_hands(beaker)
+ else
+ beaker.forceMove(drop_location())
+ return TRUE
/obj/machinery/cryo_cell/can_interact(mob/user)
- return ..() && user.loc != src
+ //must not be in the machine or on its turf to interact
+ if(get_turf(user) == get_turf(src))
+ return FALSE
+ return ..()
/obj/machinery/cryo_cell/CtrlClick(mob/user)
if(can_interact(user) && !state_open)
- if(set_on(!on))
- balloon_alert(user, "turned [on ? "on" : "off"]")
+ set_on(!on)
+ balloon_alert(user, "turned [on ? "on" : "off"]")
return ..()
/obj/machinery/cryo_cell/AltClick(mob/user)
if(can_interact(user))
- balloon_alert(user, "[state_open ? "closing" : "opening"] door")
if(state_open)
close_machine()
else
open_machine()
+ balloon_alert(user, "door [state_open ? "opened" : "closed"]")
return ..()
/obj/machinery/cryo_cell/get_remote_view_fullscreens(mob/user)
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
-/obj/machinery/cryo_cell/return_temperature()
- var/datum/gas_mixture/G = internal_connector.gas_connector.airs[1]
-
- if(G.total_moles() > 10)
- return G.temperature
- return ..()
-
#undef MAX_TEMPERATURE
#undef CRYO_MULTIPLY_FACTOR
#undef CRYO_TX_QTY
diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm
index b5a43adb4cb..6c036d37bef 100644
--- a/code/modules/reagents/chemistry/machinery/chem_heater.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm
@@ -34,12 +34,31 @@
QDEL_NULL(beaker)
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/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))
+ return NONE
+
+ if(!QDELETED(beaker))
+ if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe))
+ context[SCREENTIP_CONTEXT_LMB] = "Inject"
+ return CONTEXTUAL_SCREENTIP_SET
+ if(is_reagent_container(held_item) && held_item.is_open_container())
+ context[SCREENTIP_CONTEXT_LMB] = "Replace beaker"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(is_reagent_container(held_item) && held_item.is_open_container())
+ context[SCREENTIP_CONTEXT_LMB] = "Insert beaker"
+ return CONTEXTUAL_SCREENTIP_SET
+
+ if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "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
+
+ return NONE
+
/obj/machinery/chem_heater/update_icon_state()
icon_state = "[base_icon_state][beaker ? 1 : 0]b"
@@ -54,6 +73,13 @@
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)
@@ -116,30 +142,6 @@
for(var/datum/tgui/ui in src.open_uis)
ui.send_update()
-/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))
- return NONE
-
- if(!QDELETED(beaker))
- if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe))
- context[SCREENTIP_CONTEXT_LMB] = "Inject"
- return CONTEXTUAL_SCREENTIP_SET
- if(is_reagent_container(held_item) && held_item.is_open_container())
- context[SCREENTIP_CONTEXT_LMB] = "Replace beaker"
- return CONTEXTUAL_SCREENTIP_SET
- else if(is_reagent_container(held_item) && held_item.is_open_container())
- context[SCREENTIP_CONTEXT_LMB] = "Insert beaker"
- return CONTEXTUAL_SCREENTIP_SET
-
- if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
- context[SCREENTIP_CONTEXT_LMB] = "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
-
- return NONE
-
/obj/machinery/chem_heater/examine(mob/user)
. = ..()
if(in_range(user, src) || isobserver(user))
@@ -220,13 +222,12 @@
if(!QDELETED(beaker))
beaker_data = list()
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
chem_temp = beaker.reagents.chem_temp
.["beaker"] = beaker_data
@@ -234,15 +235,13 @@
var/list/active_reactions = list()
var/flashing = DISABLE_FLASHING //for use with alertAfter - since there is no alertBefore, I set the after to 0 if true, or to the max value if false
- for(var/_reaction in beaker?.reagents.reaction_list)
- var/datum/equilibrium/equilibrium = _reaction
+ for(var/datum/equilibrium/equilibrium as anything in beaker?.reagents.reaction_list)
if(!equilibrium.reaction.results)//Incase of no result reactions
continue
- var/_reagent = equilibrium.reaction.results[1]
- var/datum/reagent/reagent = beaker?.reagents.has_reagent(_reagent) //Reactions are named after their primary products
+ var/datum/reagents/beaker_reagents = beaker.reagents
+ var/datum/reagent/reagent = beaker_reagents.has_reagent(equilibrium.reaction.results[1]) //Reactions are named after their primary products
if(!reagent)
continue
- var/datum/reagents/beaker_reagents = beaker.reagents
//check for danger levels primirarly overheating
var/overheat = FALSE
diff --git a/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
index b5f2d31dfa5..9d81188fb40 100644
--- a/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_recipe_debug.dm
@@ -2,394 +2,240 @@
* A debug chem tester that will process through all recipies automatically and try to react them.
* Highlights low purity reactions and and reactions that don't happen
*/
+
+///don't alter the temperatrue of the reaction
+#define USE_REACTION_TEMPERATURE 0
+///force a user specified value for temperature on the reaction
+#define USE_USER_TEMPERATURE 1
+///force the minimum required temperature for the reaction to start on the reaction
+#define USE_MINIMUM_TEMPERATURE 2
+///force the optimal temperature for the reaction
+#define USE_OPTIMAL_TEMPERATURE 3
+///force the overheat temperature for the reaction. At this point reagents start to decrease
+#define USE_OVERHEAT_TEMPERATURE 4
+
+///Play the next reaction i.e. increment current_reaction_index
+#define PLAY_NEXT_REACTION 0
+///Play the previous reaction i.e. decrement current_reaction_index
+#define PLAY_PREVIOUS_REACTION 1
+///Pick a reaction at random i.e. user decides via input list what the value of current_reaction_index should be
+#define PLAY_USER_REACTION 2
+
+///Maximum volume of reagents this machine & its required container can hold
+#define MAXIMUM_HOLDER_VOLUME 9000
+
/obj/machinery/chem_recipe_debug
name = "chemical reaction tester"
- density = TRUE
icon = 'icons/obj/medical/chemical.dmi'
icon_state = "HPLC_debug"
+ density = TRUE
idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.4
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
- ///List of every reaction in the game kept locally for easy access
- var/list/cached_reactions = list()
- ///What index in the cached_reactions we're in
- var/index = 1
- ///If the machine is currently processing through the list
- var/processing = FALSE
- ///Final output that highlights all of the reactions with inoptimal purity/voolume at base
- var/problem_string
- ///Final output that highlights all of the reactions with inoptimal purity/voolume at base
- var/impure_string
- ///The count of reactions that resolve between 1 - 0.9 purity
- var/minorImpurity
- ///The count of reactions that resolve below 0.9 purity
- var/majorImpurity
- ///If we failed to react this current chem so use a lower temp - all reactions only
- var/failed = 0
- ///If we're forcing optimal conditions
- var/should_force_temp = FALSE
- var/should_force_ph = FALSE
- ///Forced values
- var/force_temp = 300
- var/force_ph = 7
- ///Multiplier of product
- var/vol_multiplier = 20
- ///If we're reacting
- var/react = FALSE
- ///Number of delta times taken to react
- var/react_time = 0
- ///IF we're doing EVERY reaction
- var/process_all = FALSE
- ///The name
- var/list/reaction_names = list()
- ///If it's started
- var/reaction_stated = FALSE
- ///If we spawn a beaker at the end of a reaction or not
- var/beaker_spawn = FALSE
- ///If we force min temp on reaction setup
- var/min_temp = FALSE
- ///The recipe we're editing
- var/datum/chemical_reaction/edit_recipe
-///Create reagents datum
+ ///Temperature to be imposed on the reaction
+ var/forced_temp = DEFAULT_REAGENT_TEMPERATURE
+ ///The mode for setting reaction temps. see temp defines
+ var/temp_mode = USE_REACTION_TEMPERATURE
+ ///The ph to be imposed on the reaction
+ var/forced_ph = CHEMICAL_NORMAL_PH
+ ///if TRUE will use forced_ph else don't alter the ph of the reaction
+ var/use_forced_ph = FALSE
+ ///The purity of all reagents to be imposed on the reaction
+ var/forced_purity = 1.0
+ ///If TRUE will use forced_purity else don't alter the purity of the reaction
+ var/use_forced_purity = FALSE
+ ///The multiplier to be applied on the selected reaction required reagents to start the reaction
+ var/volume_multiplier = 1
+
+ ///Cached copy all reactions mapped with their name
+ var/static/list/all_reaction_list
+ ///The list of reactions to test
+ var/list/datum/chemical_reaction/reactions_to_test = list()
+ ///The index in reactions_to_test list which points to the current reaction under test
+ var/current_reaction_index = 0
+ ///Decides which reaction to play in the reactions_to_test list see Play defines
+ var/current_reaction_mode = PLAY_NEXT_REACTION
+ ///The current reaction we are editing
+ var/datum/chemical_reaction/edit_reaction
+ ///The current var of the reaction we are editing
+ var/edit_var = "Required Temp"
+
+ ///The target reagents to we are working with. can vary if an reaction requires a specific container
+ var/datum/reagents/target_reagents
+ ///The beaker inside this machine, if null will create a new one
+ var/obj/item/reagent_containers/cup/beaker/bluespace/beaker
+ ///The default reagent container required for the selected test reaction if any
+ var/obj/item/reagent_containers/required_container
+
/obj/machinery/chem_recipe_debug/Initialize(mapload)
. = ..()
- create_reagents(9000)//I want to make sure everything fits
- end_processing()
-///Enable the machine
-/obj/machinery/chem_recipe_debug/attackby(obj/item/I, mob/user, params)
+ create_reagents(MAXIMUM_HOLDER_VOLUME)
+ target_reagents = reagents
+ RegisterSignal(reagents, COMSIG_REAGENTS_REACTION_STEP, TYPE_PROC_REF(/obj/machinery/chem_recipe_debug, on_reaction_step))
+ register_context()
+
+ if(isnull(all_reaction_list))
+ all_reaction_list = list()
+ for(var/datum/reagent/reagent as anything in GLOB.chemical_reactions_list_reactant_index)
+ for(var/datum/chemical_reaction/reaction as anything in GLOB.chemical_reactions_list_reactant_index[reagent])
+ all_reaction_list[extract_reaction_name(reaction)] = reaction
+
+/obj/machinery/chem_recipe_debug/Destroy()
+ reactions_to_test.Cut()
+ target_reagents = null
+ edit_reaction = null
+ QDEL_NULL(beaker)
+ QDEL_NULL(required_container)
+ UnregisterSignal(reagents, COMSIG_REAGENTS_REACTION_STEP)
. = ..()
- ui_interact(usr)
-///Enable the machine
-/obj/machinery/chem_recipe_debug/AltClick(mob/living/user)
+/obj/machinery/chem_recipe_debug/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
- if(processing)
- say("currently processing reaction [index]: [cached_reactions[index]] of [cached_reactions.len]")
- return
- process_all = TRUE
- say("Starting processing")
- setup_reactions()
- begin_processing()
+ if(isnull(held_item) || (held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
+ return NONE
-///Resets the index, and creates the cached_reaction list from all possible reactions
-/obj/machinery/chem_recipe_debug/proc/setup_reactions()
- cached_reactions = list()
- if(process_all)
- for(var/reaction in GLOB.chemical_reactions_list_reactant_index)
- if(is_type_in_list(GLOB.chemical_reactions_list_reactant_index[reaction], cached_reactions))
- continue
- cached_reactions += GLOB.chemical_reactions_list_reactant_index[reaction]
+ if(!QDELETED(beaker))
+ if(is_reagent_container(held_item) && held_item.is_open_container())
+ context[SCREENTIP_CONTEXT_LMB] = "Replace beaker"
+ return CONTEXTUAL_SCREENTIP_SET
+ else if(is_reagent_container(held_item) && held_item.is_open_container())
+ context[SCREENTIP_CONTEXT_LMB] = "Insert beaker"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/machinery/chem_recipe_debug/examine(mob/user)
+ . = ..()
+ if(!QDELETED(beaker))
+ . += span_notice("A beaker of [beaker.reagents.maximum_volume]u capacity is inside.")
else
- cached_reactions = reaction_names
- reagents.clear_reagents()
- index = 1
- processing = TRUE
+ . += span_notice("No beaker is present. A new will be created when ejecting.")
+
+/obj/machinery/chem_recipe_debug/Exited(atom/movable/gone, direction)
+ . = ..()
+ if(gone == beaker)
+ beaker = null
+
+/obj/machinery/chem_recipe_debug/attackby(obj/item/held_item, mob/user, params)
+ if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
+ return ..()
+
+ if(is_reagent_container(held_item) && held_item.is_open_container())
+ . = TRUE
+ if(!QDELETED(beaker))
+ try_put_in_hand(beaker, user)
+ if(!user.transferItemToLoc(held_item, src))
+ return
+ beaker = held_item
+
+/**
+ * Extracts a human readable name for this chemical reaction
+ * Arguments
+ *
+ * * datum/chemical_reaction/reaction - the reaction who's name we have to decode
+ */
+/obj/machinery/chem_recipe_debug/proc/extract_reaction_name(datum/chemical_reaction/reaction)
+ PRIVATE_PROC(TRUE)
+ SHOULD_BE_PURE(TRUE)
+
+ var/reaction_name = "[reaction]"
+ reaction_name = copytext(reaction_name, findlasttext(reaction_name, "/") + 1)
+ reaction_name = replacetext(reaction_name, "_", " ")
+ return full_capitalize(reaction_name)
+
+///Retrives the target temperature to be imposed on the test reaction based on temp_mode
+/obj/machinery/chem_recipe_debug/proc/decode_target_temperature()
+ PRIVATE_PROC(TRUE)
+ SHOULD_BE_PURE(TRUE)
+
+ if(temp_mode == USE_REACTION_TEMPERATURE)
+ return null //simply means don't alter the reaction temperature
+ else if(temp_mode == USE_USER_TEMPERATURE)
+ return forced_temp
+ else
+ var/datum/chemical_reaction/test_reaction = reactions_to_test[current_reaction_index || 1]
+ switch(temp_mode)
+ if(USE_MINIMUM_TEMPERATURE)
+ return test_reaction.required_temp + (test_reaction.is_cold_recipe ? - 20 : 20) //20k is good enough offset to account for reaction rate rounding
+ if(USE_OPTIMAL_TEMPERATURE)
+ return test_reaction.optimal_temp
+ if(USE_OVERHEAT_TEMPERATURE)
+ return test_reaction.overheat_temp
+
+
+/**
+ * Adjusts the temperature, ph & purity of the holder
+ * Arguments
+ *
+ * * seconds_per_tick - passed from on_reaction_step or process
+ */
+/obj/machinery/chem_recipe_debug/proc/adjust_environment(seconds_per_tick)
+ PRIVATE_PROC(TRUE)
+
+ var/target_temperature = decode_target_temperature()
+ if(!isnull(target_temperature))
+ target_reagents.adjust_thermal_energy((target_temperature - target_reagents.chem_temp) * 0.4 * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * target_reagents.total_volume)
+
+ if(use_forced_purity)
+ target_reagents.set_all_reagents_purity(forced_purity)
+
+ if(use_forced_ph)
+ for(var/datum/reagent/reagent as anything in target_reagents.reagent_list)
+ reagent.ph = clamp(forced_ph, CHEMICAL_MIN_PH, CHEMICAL_MAX_PH)
+
+ target_reagents.update_total()
-/*
-* The main loop that sets up, creates and displays results from a reaction
-* warning: this code is a hot mess
-*/
/obj/machinery/chem_recipe_debug/process(seconds_per_tick)
- if(processing == FALSE)
- setup_reactions()
- if(should_force_ph)
- reagents.ph = force_ph
- if(should_force_temp)
- reagents.chem_temp = force_temp
- if(reagents.is_reacting == TRUE)
- react_time += seconds_per_tick
+ if(!target_reagents.is_reacting)
+ adjust_environment(seconds_per_tick)
+ target_reagents.handle_reactions()
+
+ //send updates to ui. faster than SStgui.update_uis
+ for(var/datum/tgui/ui in src.open_uis)
+ ui.send_update()
+
+/obj/machinery/chem_recipe_debug/proc/on_reaction_step(datum/reagents/holder, num_reactions, seconds_per_tick)
+ SIGNAL_HANDLER
+
+ adjust_environment(seconds_per_tick)
+
+ //send updates to ui. faster than SStgui.update_uis
+ for(var/datum/tgui/ui in src.open_uis)
+ ui.send_update()
+
+/**
+ * Decodes the ui reaction var into it's original name
+ * Arguments
+ *
+ * * variable - the name of the variable as seen in the UI
+ */
+/obj/machinery/chem_recipe_debug/proc/decode_var(variable)
+ PRIVATE_PROC(TRUE)
+
+ . = null
+
+ if(isnull(edit_reaction))
return
- if(reaction_stated == TRUE)
- reaction_stated = FALSE
- relay_ended_reaction()
- if(index > cached_reactions.len)
- relay_all_reactions()
- return
- setup_reaction()
- reaction_stated = TRUE
-/obj/machinery/chem_recipe_debug/proc/relay_all_reactions()
- say("Completed testing, missing reactions products (may have exploded) are:")
- say("[problem_string]", sanitize=FALSE)
- say("Problem with results are:")
- say("[impure_string]", sanitize=FALSE)
- say("Reactions with minor impurity: [minorImpurity], reactions with major impurity: [majorImpurity]")
- processing = FALSE
- problem_string = null
- impure_string = null
- minorImpurity = null
- majorImpurity = null
- end_processing()
-
-/obj/machinery/chem_recipe_debug/proc/relay_ended_reaction()
- if(reagents.reagent_list)
- var/cached_purity
- say("Reaction completed for [cached_reactions[index]] final temperature = [reagents.chem_temp], ph = [reagents.ph], time taken = [react_time]s.")
- var/datum/chemical_reaction/reaction = cached_reactions[index]
- for(var/reagent_type in reaction.results)
- var/datum/reagent/reagent = reagents.has_reagent(reagent_type)
- if(!reagent)
- say(span_warning("Unable to find product [reagent_type] in holder after reaction! reagents found are:"))
- for(var/other_reagent in reagents.reagent_list)
- say("[other_reagent]")
- var/obj/item/reagent_containers/cup/beaker/bluespace/beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(loc)
- reagents.trans_to(beaker)
- beaker.name = "[cached_reactions[index]] failed"
- if(!failed)
- problem_string += "[cached_reactions[index]] [span_warning("Unable to find product [reagent_type] in holder after reaction! Trying alternative setup. index:[index]")]\n"
- failed++
- return
- say("Reaction has a product [reagent_type] [reagent.volume]u purity of [reagent.purity]")
- if(reagent.purity < 0.9)
- impure_string += "Reaction [cached_reactions[index]] has a product [reagent_type] [reagent.volume]u [span_boldwarning("purity of [reagent.purity]")] index:[index]\n"
- majorImpurity++
- else if (reagent.purity < 1)
- impure_string += "Reaction [cached_reactions[index]] has a product [reagent_type] [reagent.volume]u [span_warning("purity of [reagent.purity]")] index:[index]\n"
- minorImpurity++
- if(reagent.volume < reaction.results[reagent_type])
- impure_string += "Reaction [cached_reactions[index]] has a product [reagent_type] [span_warning("[reagent.volume]u")] purity of [reagent.purity] index:[index]\n"
- cached_purity = reagent.purity
- if(beaker_spawn && reagents.total_volume)
- var/obj/item/reagent_containers/cup/beaker/bluespace/beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(loc)
- reagents.trans_to(beaker)
- beaker.name = "[cached_reactions[index]] purity: [cached_purity]"
- reagents.clear_reagents()
- reagents.chem_temp = 300
- index++
- failed = 0
- else
- say("No reagents left in beaker!")
- index++
-
-/obj/machinery/chem_recipe_debug/proc/setup_reaction()
- react_time = 0
- if(!length(cached_reactions))
- return FALSE
- var/datum/chemical_reaction/reaction = cached_reactions[index]
- if(!reaction)
- say("Unable to find reaction on index: [index]")
- say("Using forced temperatures.")
- if(reaction.reaction_flags & REACTION_INSTANT)
- say("This reaction is instant")
- for(var/reagent_type in reaction.required_reagents)
- reagents.add_reagent(reagent_type, reaction.required_reagents[reagent_type]*vol_multiplier)
- for(var/catalyst_type in reaction.required_catalysts)
- reagents.add_reagent(catalyst_type, reaction.required_catalysts[catalyst_type])
- if(should_force_temp && !min_temp)
- say("Using forced temperatures.")
- reagents.chem_temp = force_temp ? force_temp : reaction.optimal_temp
- if(should_force_ph)
- say("Using forced pH.")
- reagents.ph = force_ph ? force_ph : (reaction.optimal_ph_max + reaction.optimal_ph_min)/2
- if(failed == 0 && !should_force_temp)
- reagents.chem_temp = reaction.optimal_temp
- if(failed == 1 && !should_force_temp)
- reagents.chem_temp = reaction.required_temp+25
- failed++
- if(min_temp)
- say("Overriding temperature to required temp.")
- reagents.chem_temp = reaction.is_cold_recipe ? reaction.required_temp - 1 : reaction.required_temp + 1
- say("Reacting [span_nicegreen("[cached_reactions[index]]")] starting pH: [reagents.ph] index [index] of [cached_reactions.len]")
-
-/obj/machinery/chem_recipe_debug/ui_data(mob/user)
- var/data = list()
- data["targetTemp"] = force_temp
- data["targatpH"] = force_ph
- data["isActive"] = reagents.is_reacting
- data["forcepH"] = should_force_ph
- data["forceTemp"] = should_force_temp
- data["targetVol"] = vol_multiplier
- data["processAll"] = process_all
- data["currentTemp"] = reagents.chem_temp
- data["currentpH"] = round(reagents.ph, 0.01)
- data["processing"] = processing
- data["index"] = index
- data["endIndex"] = cached_reactions.len
- data["beakerSpawn"] = beaker_spawn
- data["minTemp"] = min_temp
- data["editRecipe"] = null
-
- var/list/beaker_contents = list()
- for(var/datum/reagent/reagent as anything in reagents.reagent_list)
- beaker_contents.len++
- beaker_contents[length(beaker_contents)] = list("name" = reagent.name, "volume" = round(reagent.volume, 0.01), "purity" = round(reagent.purity))
- data["chamberContents"] = beaker_contents
-
- var/list/queued_reactions = list()
- for(var/datum/chemical_reaction/reaction as anything in reaction_names)
- var/datum/reagent/reagent = find_reagent_object_from_type(reaction.results[1])
- queued_reactions.len++
- queued_reactions[length(queued_reactions)] = list("name" = reagent.name)
- data["queuedReactions"] = queued_reactions
-
- var/list/active_reactions = list()
- var/flashing = DISABLE_FLASHING //for use with alertAfter - since there is no alertBefore, I set the after to 0 if true, or to the max value if false
- for(var/datum/equilibrium/equilibrium as anything in reagents.reaction_list)
- if(!length(reagents.reaction_list))//I'm not sure why when it explodes it causes the gui to fail (it's missing danger (?) )
- stack_trace("Chem debug managed to find an equilibrium in a location where there should be none (skipping this entry and continuing). This is usually because of an ill timed explosion.")
- continue
- if(!equilibrium.reaction.results)//Incase of no result reactions
- continue
- var/datum/reagent/reagent = reagents.has_reagent(equilibrium.reaction.results[1]) //Reactions are named after their primary products
- if(!reagent)
- continue
- var/overheat = FALSE
- var/danger = FALSE
- var/purity_alert = 2 //same as flashing
- if(reagent.purity < equilibrium.reaction.purity_min)
- purity_alert = ENABLE_FLASHING//Because 0 is seen as null
- danger = TRUE
- if(flashing != ENABLE_FLASHING)//So that the pH meter flashes for ANY reactions out of optimal
- if(equilibrium.reaction.optimal_ph_min > reagents.ph || equilibrium.reaction.optimal_ph_max < reagents.ph)
- flashing = ENABLE_FLASHING
- if(equilibrium.reaction.is_cold_recipe)
- if(equilibrium.reaction.overheat_temp > reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT)
- danger = TRUE
- overheat = TRUE
- else
- if(equilibrium.reaction.overheat_temp < reagents.chem_temp)
- danger = TRUE
- overheat = TRUE
- if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE) //We have a compeitive reaction - concatenate the results for the different reactions
- for(var/entry in active_reactions)
- if(entry["name"] == reagent.name) //If we have multiple reaction methods for the same result - combine them
- entry["reactedVol"] = equilibrium.reacted_vol
- entry["targetVol"] = round(equilibrium.target_vol, 1)//Use the first result reagent to name the reaction detected
- entry["quality"] = (entry["quality"] + equilibrium.reaction_quality) /2
- continue
- active_reactions.len++
- active_reactions[length(active_reactions)] = list("name" = reagent.name, "danger" = danger, "purityAlert" = purity_alert, "quality" = equilibrium.reaction_quality, "overheat" = overheat, "inverse" = reagent.inverse_chem_val, "minPure" = equilibrium.reaction.purity_min, "reactedVol" = equilibrium.reacted_vol, "targetVol" = round(equilibrium.target_vol, 1))//Use the first result reagent to name the reaction detected
- data["activeReactions"] = active_reactions
- data["isFlashing"] = flashing
-
- if(edit_recipe)
- data["editRecipeName"] = edit_recipe.type
- data["editRecipeCold"] = edit_recipe.is_cold_recipe
- data["editRecipe"] = list(
- list("name" = "required_temp" , "var" = edit_recipe.required_temp),
- list("name" = "optimal_temp" , "var" = edit_recipe.optimal_temp),
- list("name" = "overheat_temp" , "var" = edit_recipe.overheat_temp),
- list("name" = "optimal_ph_min" , "var" = edit_recipe.optimal_ph_min),
- list("name" = "optimal_ph_max" , "var" = edit_recipe.optimal_ph_max),
- list("name" = "determin_ph_range" , "var" = edit_recipe.determin_ph_range),
- list("name" = "temp_exponent_factor" , "var" = edit_recipe.temp_exponent_factor),
- list("name" = "ph_exponent_factor" , "var" = edit_recipe.ph_exponent_factor),
- list("name" = "thermic_constant" , "var" = edit_recipe.thermic_constant),
- list("name" = "H_ion_release" , "var" = edit_recipe.H_ion_release),
- list("name" = "rate_up_lim" , "var" = edit_recipe.rate_up_lim),
- list("name" = "purity_min" , "var" = edit_recipe.purity_min),
+ var/static/list/ui_to_var
+ if(isnull(ui_to_var))
+ ui_to_var = list(
+ "Required Temp" = "required_temp",
+ "Optimal Temp" = "optimal_temp",
+ "Overheat Temp" = "overheat_temp",
+ "Optimal Min Ph" = "optimal_ph_min",
+ "Optimal Max Ph" = "optimal_ph_max",
+ "Ph Range" = "determin_ph_range",
+ "Temp Exp Factor" = "temp_exponent_factor",
+ "Ph Exp Factor" = "ph_exponent_factor",
+ "Thermic Constant" = "thermic_constant",
+ "H Ion Release" = "H_ion_release",
+ "Rate Up Limit" = "rate_up_lim",
+ "Purity Min" = "purity_min",
)
- return data
-
-/obj/machinery/chem_recipe_debug/ui_act(action, params)
- . = ..()
- if(.)
- return
- switch(action)
- if("power")
- return
- if("temperature")
- var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target)
- . = TRUE
- if(.)
- force_temp = clamp(target, 0, 1000)
- if("pH")
- var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target)
- . = TRUE
- if(.)
- force_ph = target
- if("forceTemp")
- should_force_temp = ! should_force_temp
- . = TRUE
- if("forcepH")
- should_force_ph = ! should_force_ph
- . = TRUE
- if("react")
- react = TRUE
- return TRUE
- if("all")
- process_all = !process_all
- return TRUE
- if("beakerSpawn")
- beaker_spawn = !beaker_spawn
- return TRUE
- if("setTargetList")
- var/text = tgui_input_text(usr, "Enter a list of Recipe product names separated by commas", "Recipe List", multiline = TRUE)
- reaction_names = list()
- if(!text)
- say("Could not find reaction")
- var/list/names = splittext("[text]", ",")
- for(var/name in names)
- var/datum/reagent/reagent = find_reagent_object_from_type(get_chem_id(name))
- if(!reagent)
- say("Could not find [name]")
- continue
- var/datum/chemical_reaction/reaction = GLOB.chemical_reactions_list_product_index[reagent.type]
- if(!reaction)
- say("Could not find [name] reaction!")
- continue
- reaction_names += reaction
- if("vol")
- var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target)
- . = TRUE
- if(.)
- vol_multiplier = clamp(target, 1, 200)
- if("start")
- if(processing)
- say("currently processing reaction [index]: [cached_reactions[index]] of [cached_reactions.len]")
- return
- say("Starting processing")
- index = 1
- setup_reactions()
- begin_processing()
- return TRUE
- if("stop")
- relay_all_reactions()
- if("minTemp")
- min_temp = !min_temp
- if("setEdit")
- var/name = (input("Enter the name of any reagent", "Input") as text|null)
- reaction_names = list()
- if(!text)
- say("Could not find reaction")
- var/datum/reagent/reagent = find_reagent_object_from_type(get_chem_id(name))
- if(!reagent)
- say("Could not find [name]")
- return
- var/datum/chemical_reaction/reaction = GLOB.chemical_reactions_list_product_index[reagent.type]
- if(!reaction)
- say("Could not find [name] reaction!")
- return
- edit_recipe = reaction[1]
- if("updateVar")
- var/target = params["target"]
- edit_recipe.vars[params["type"]] = target
- if("export")
- var/export = {"[edit_recipe.type]
-[edit_recipe.is_cold_recipe ? "is_cold_recipe = TRUE" : ""]
-required_temp = [edit_recipe.required_temp]
-optimal_temp = [edit_recipe.optimal_temp]
-overheat_temp = [edit_recipe.overheat_temp]
-optimal_ph_min = [edit_recipe.optimal_ph_min]
-optimal_ph_max = [edit_recipe.optimal_ph_max]
-determin_ph_range = [edit_recipe.determin_ph_range]
-temp_exponent_factor = [edit_recipe.temp_exponent_factor]
-ph_exponent_factor = [edit_recipe.ph_exponent_factor]
-thermic_constant = [edit_recipe.thermic_constant]
-H_ion_release = [edit_recipe.H_ion_release]
-rate_up_lim = [edit_recipe.rate_up_lim]
-purity_min = [edit_recipe.purity_min]"}
- say(export)
- text2file(export, "[GLOB.log_directory]/chem_parse.txt")
-
+ var/value = ui_to_var[variable]
+ if(!isnull(value))
+ . = value
/obj/machinery/chem_recipe_debug/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -397,10 +243,443 @@ purity_min = [edit_recipe.purity_min]"}
ui = new(user, src, "ChemRecipeDebug", name)
ui.open()
-///Moves a type of buffer from the heater to the beaker,
+/obj/machinery/chem_recipe_debug/ui_data(mob/user)
+ . = list()
-/obj/machinery/chem_recipe_debug/ui_status(mob/user)
- return UI_INTERACTIVE
+ .["forced_temp"] = forced_temp
+ .["temp_mode"] = temp_mode
+ .["forced_ph"] = forced_ph
+ .["use_forced_ph"] = use_forced_ph
+ .["forced_purity"] = forced_purity
+ .["use_forced_purity"] = use_forced_purity
+ .["volume_multiplier"] = volume_multiplier
-/obj/machinery/chem_recipe_debug/ui_state(mob/user)
- return GLOB.physical_state
+ var/datum/chemical_reaction/current_reaction = null
+ if(reactions_to_test.len)
+ current_reaction = reactions_to_test[current_reaction_index || 1]
+ if(isnull(current_reaction))
+ .["current_reaction_name"] = "N/A"
+ else
+ .["current_reaction_name"] = extract_reaction_name(current_reaction)
+ .["current_reaction_mode"] = current_reaction_mode
+
+ var/list/active_reactions = list()
+ var/flashing = DISABLE_FLASHING //for use with alertAfter - since there is no alertBefore, I set the after to 0 if true, or to the max value if false
+ for(var/datum/equilibrium/equilibrium as anything in target_reagents.reaction_list)
+ if(!equilibrium.reaction.results)//Incase of no result reactions
+ continue
+ var/datum/reagent/reagent = target_reagents.has_reagent(equilibrium.reaction.results[1]) //Reactions are named after their primary products
+ if(!reagent)
+ continue
+
+ //check for danger levels primirarly overheating
+ var/overheat = FALSE
+ var/danger = FALSE
+ var/purity_alert = 2 //same as flashing
+ if(reagent.purity < equilibrium.reaction.purity_min)
+ purity_alert = ENABLE_FLASHING//Because 0 is seen as null
+ danger = TRUE
+ if(flashing != ENABLE_FLASHING)//So that the pH meter flashes for ANY reactions out of optimal
+ if(equilibrium.reaction.optimal_ph_min > target_reagents.ph || equilibrium.reaction.optimal_ph_max < target_reagents.ph)
+ flashing = ENABLE_FLASHING
+ if(equilibrium.reaction.is_cold_recipe)
+ if(equilibrium.reaction.overheat_temp > target_reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT)
+ danger = TRUE
+ overheat = TRUE
+ else
+ if(equilibrium.reaction.overheat_temp < target_reagents.chem_temp)
+ danger = TRUE
+ overheat = TRUE
+
+ //create ui data
+ active_reactions += list(list(
+ "name" = reagent.name,
+ "danger" = danger,
+ "overheat" = overheat,
+ "purityAlert" = purity_alert,
+ "quality" = equilibrium.reaction_quality,
+ "inverse" = reagent.inverse_chem_val,
+ "minPure" = equilibrium.reaction.purity_min,
+ "reactedVol" = equilibrium.reacted_vol,
+ "targetVol" = round(equilibrium.target_vol, 1)
+ )
+ )
+
+ //additional data for competitive reactions
+ if(equilibrium.reaction.reaction_flags & REACTION_COMPETITIVE) //We have a compeitive reaction - concatenate the results for the different reactions
+ for(var/entry in active_reactions)
+ if(entry["name"] == reagent.name) //If we have multiple reaction methods for the same result - combine them
+ entry["reactedVol"] = equilibrium.reacted_vol
+ entry["targetVol"] = round(equilibrium.target_vol, 1)//Use the first result reagent to name the reaction detected
+ entry["quality"] = (entry["quality"] + equilibrium.reaction_quality) /2
+ continue
+ .["activeReactions"] = active_reactions
+
+ .["isFlashing"] = flashing
+ .["isReacting"] = target_reagents.is_reacting
+
+ var/list/reaction_data = null
+ if(!isnull(edit_reaction))
+ var/reaction_name
+ if(length(edit_reaction.results)) //soups can have no results
+ var/datum/reagent/reagent = edit_reaction.results[1]
+ reaction_name = initial(reagent.name)
+ else
+ reaction_name = "[edit_reaction]"
+ reaction_data = list(
+ "name" = reaction_name,
+ "editVar" = edit_var,
+ "editValue" = edit_reaction.vars[decode_var(edit_var)]
+ )
+ .["editReaction"] = reaction_data
+
+ var/list/beaker_data = null
+ if(target_reagents.reagent_list.len)
+ beaker_data = list()
+ beaker_data["maxVolume"] = target_reagents.maximum_volume
+ beaker_data["pH"] = round(target_reagents.ph, 0.01)
+ beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.01)
+ beaker_data["currentVolume"] = round(target_reagents.total_volume, CHEMICAL_VOLUME_ROUNDING)
+ beaker_data["currentTemp"] = round(target_reagents.chem_temp, 1)
+ beaker_data["purity"] = round(target_reagents.get_average_purity(), 0.001)
+ var/list/beakerContents = list()
+ if(length(target_reagents.reagent_list))
+ for(var/datum/reagent/reagent in target_reagents.reagent_list)
+ beakerContents += list(list("name" = reagent.name, "volume" = round(reagent.volume, CHEMICAL_VOLUME_ROUNDING)))
+
+ if(!QDELETED(required_container))
+ //as of now we only decode soup pots. If more exotic containers are made make sure to add them here
+ if(istype(required_container, /obj/item/reagent_containers/cup/soup_pot))
+ var/obj/item/reagent_containers/cup/soup_pot/pot = required_container
+ for(var/obj/item as anything in pot.added_ingredients)
+ //increment count if item already exists
+ var/entry_found = FALSE
+ for(var/list/entry as anything in beakerContents)
+ if(entry["name"] == item.name)
+ entry["volume"] += 1
+ entry_found = TRUE
+ break
+ //new entry if non existent
+ if(!entry_found)
+ beakerContents += list(list("name" = item.name, "volume" = 1))
+
+ beaker_data["contents"] = beakerContents
+ .["beaker"] = beaker_data
+
+/obj/machinery/chem_recipe_debug/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("forced_temp")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+
+ target = text2num(target)
+ if(isnull(target))
+ return
+
+ forced_temp = target
+ return TRUE
+
+ if("temp_mode")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+
+ switch(target)
+ if("Reaction Temp")
+ temp_mode = USE_REACTION_TEMPERATURE
+ return TRUE
+ if("Forced Temp")
+ temp_mode = USE_USER_TEMPERATURE
+ return TRUE
+ if("Minimum Temp")
+ temp_mode = USE_MINIMUM_TEMPERATURE
+ return TRUE
+ if("Optimal Temp")
+ temp_mode = USE_OPTIMAL_TEMPERATURE
+ return TRUE
+ if("Overheat Temp")
+ temp_mode = USE_OVERHEAT_TEMPERATURE
+ return TRUE
+
+ if("forced_ph")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+
+ target = text2num(target)
+ if(isnull(target))
+ return
+
+ forced_ph = target
+ return TRUE
+
+ if("toggle_forced_ph")
+ use_forced_ph = !use_forced_ph
+ return TRUE
+
+ if("forced_purity")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+
+ target = text2num(target)
+ if(isnull(target))
+ return
+
+ forced_purity = target
+ return TRUE
+
+ if("toggle_forced_purity")
+ use_forced_purity = !use_forced_purity
+ return TRUE
+
+ if("volume_multiplier")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+
+ target = text2num(target)
+ if(isnull(target))
+ return
+
+ volume_multiplier = target
+ return TRUE
+
+ if("pick_reaction")
+ var/mode = tgui_alert(usr, "Play all or an specific reaction?","Select Reaction", list("All", "Specific"))
+ if(mode == "All")
+ reactions_to_test.Cut()
+ for(var/reaction as anything in all_reaction_list)
+ reactions_to_test += all_reaction_list[reaction]
+ current_reaction_index = 0
+ return TRUE
+
+ var/selected_reaction = tgui_input_list(ui.user, "Select Reaction", "Reaction", all_reaction_list)
+ if(!selected_reaction)
+ return
+
+ var/datum/chemical_reaction/reaction = all_reaction_list[selected_reaction]
+ if(!reaction)
+ return
+
+ reactions_to_test.Cut()
+ reactions_to_test += reaction
+ current_reaction_index = 0
+ return TRUE
+
+ if("reaction_mode")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+
+ switch(target)
+ if("Next Reaction")
+ current_reaction_mode = PLAY_NEXT_REACTION
+ return TRUE
+ if("Previous Reaction")
+ current_reaction_mode = PLAY_PREVIOUS_REACTION
+ return TRUE
+ if("Pick Reaction")
+ current_reaction_mode = PLAY_USER_REACTION
+ return TRUE
+
+ if("start_reaction")
+ var/datum/chemical_reaction/test_reaction
+
+ //pick the reaction based on the reaction mode
+ var/len = reactions_to_test.len
+ if(len > 1)
+ switch(current_reaction_mode)
+ if(PLAY_NEXT_REACTION)
+ current_reaction_index = (current_reaction_index + 1) % len
+ if(PLAY_PREVIOUS_REACTION)
+ current_reaction_index = max(current_reaction_index - 1, 1)
+ if(PLAY_USER_REACTION)
+ var/list/reaction_names = list()
+ for(var/datum/chemical_reaction/reaction as anything in reactions_to_test)
+ reaction_names += extract_reaction_name(reaction)
+ if(!reaction_names.len)
+ return
+
+ var/selected_reaction = tgui_input_list(ui.user, "Select Reaction", "Reaction", reaction_names)
+ if(!selected_reaction)
+ return
+ for(var/i = 1; i <= reaction_names.len; i++)
+ if(selected_reaction == reaction_names[i])
+ current_reaction_index = i
+ break
+ test_reaction = reactions_to_test[current_reaction_index]
+ else if(len == 1)
+ current_reaction_index = 1
+ test_reaction = reactions_to_test[1]
+
+ //clear the previous reaction stuff
+ target_reagents.force_stop_reacting()
+ target_reagents.clear_reagents()
+
+ //If the reaction requires a specific container initialize & do other stuff accordingly
+ target_reagents = reagents
+ if(!QDELETED(required_container))
+ UnregisterSignal(required_container.reagents, COMSIG_REAGENTS_REACTION_STEP)
+ QDEL_NULL(required_container)
+ if(!isnull(test_reaction.required_container))
+ required_container = new test_reaction.required_container(src)
+ required_container.create_reagents(MAXIMUM_HOLDER_VOLUME)
+ target_reagents = required_container.reagents
+ RegisterSignal(target_reagents, COMSIG_REAGENTS_REACTION_STEP, TYPE_PROC_REF(/obj/machinery/chem_recipe_debug, on_reaction_step))
+
+ //append everything required
+ var/list/reagent_list = list()
+ if(length(test_reaction.required_catalysts))
+ reagent_list += test_reaction.required_catalysts
+ if(length(test_reaction.required_reagents))
+ reagent_list += test_reaction.required_reagents
+ //now add the required reagents
+ var/target_temperature
+ switch(temp_mode)
+ if(USE_REACTION_TEMPERATURE)
+ target_temperature = DEFAULT_REAGENT_TEMPERATURE
+ else
+ target_temperature = decode_target_temperature()
+ for(var/datum/reagent/_reagent as anything in reagent_list)
+ var/vol_mul = volume_multiplier
+ if(length(test_reaction.required_catalysts) && test_reaction.required_catalysts[_reagent.type])
+ vol_mul = 1 //catalysts don't need to be present in large amounts
+
+ //add the required reagents with the precise conditions
+ target_reagents.add_reagent(
+ _reagent,
+ reagent_list[_reagent] * vol_mul,
+ reagtemp = target_temperature,
+ added_purity = use_forced_purity ? forced_purity : null,
+ added_ph = use_forced_ph ? forced_ph : null,
+ no_react = TRUE
+ )
+
+ //add solid ingredients for soups
+ if(istype(test_reaction, /datum/chemical_reaction/food/soup))
+ var/datum/chemical_reaction/food/soup/soup_reaction = test_reaction
+ var/obj/item/reagent_containers/cup/soup_pot/pot = required_container
+ for(var/obj/item as anything in soup_reaction.required_ingredients)
+ for(var/_ in 1 to soup_reaction.required_ingredients[item])
+ LAZYADD(pot.added_ingredients, new item(pot))
+
+ target_reagents.handle_reactions()
+ return TRUE
+
+ if("edit_reaction")
+ var/selected_reaction = tgui_input_list(ui.user, "Select Reaction", "Reaction", all_reaction_list)
+ if(!selected_reaction)
+ return
+
+ var/datum/chemical_reaction/reaction = all_reaction_list[selected_reaction]
+ if(!reaction)
+ return
+
+ edit_reaction = reaction
+ edit_var = initial(edit_var)
+ return TRUE
+
+ if("edit_var")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+ if(isnull(decode_var(target)))
+ return
+ edit_var = target
+ return TRUE
+
+ if("edit_value")
+ var/target = params["target"]
+ if(isnull(target))
+ return
+
+ target = text2num(target)
+ if(isnull(target))
+ return
+
+ edit_reaction.vars[decode_var(edit_var)] = target
+ return TRUE
+
+ if("reset_value")
+ switch(edit_var)
+ if("Required Temp")
+ edit_reaction.required_temp = initial(edit_reaction.required_temp)
+ return TRUE
+ if("Optimal Temp")
+ edit_reaction.optimal_temp = initial(edit_reaction.optimal_temp)
+ return TRUE
+ if("Overheat Temp")
+ edit_reaction.overheat_temp = initial(edit_reaction.overheat_temp)
+ return TRUE
+ if("Optimal Min Ph")
+ edit_reaction.optimal_ph_min = initial(edit_reaction.optimal_ph_min)
+ return TRUE
+ if("Optimal Max Ph")
+ edit_reaction.optimal_ph_max = initial(edit_reaction.optimal_ph_max)
+ return TRUE
+ if("Ph Range")
+ edit_reaction.determin_ph_range = initial(edit_reaction.determin_ph_range)
+ return TRUE
+ if("Temp Exp Factor")
+ edit_reaction.temp_exponent_factor = initial(edit_reaction.temp_exponent_factor)
+ return TRUE
+ if("Ph Exp Factor")
+ edit_reaction.ph_exponent_factor = initial(edit_reaction.ph_exponent_factor)
+ return TRUE
+ if("Thermic Constant")
+ edit_reaction.thermic_constant = initial(edit_reaction.thermic_constant)
+ return TRUE
+ if("H Ion Release")
+ edit_reaction.H_ion_release = initial(edit_reaction.H_ion_release)
+ return TRUE
+ if("Rate Up Limit")
+ edit_reaction.rate_up_lim = initial(edit_reaction.rate_up_lim)
+ return TRUE
+ if("Purity Min")
+ edit_reaction.purity_min = initial(edit_reaction.purity_min)
+ return TRUE
+
+ if("export")
+ var/export = "[edit_reaction]\n"
+ export += "\tis_cold_recipe = [edit_reaction.is_cold_recipe]\n"
+ export += "\trequired_temp = [edit_reaction.required_temp]\n"
+ export += "\toptimal_temp = [edit_reaction.optimal_temp]\n"
+ export += "\toverheat_temp = [edit_reaction.overheat_temp]\n"
+ export += "\toptimal_ph_min = [edit_reaction.optimal_ph_min]\n"
+ export += "\toptimal_ph_max = [edit_reaction.optimal_ph_max]\n"
+ export += "\tdetermin_ph_range = [edit_reaction.determin_ph_range]\n"
+ export += "\ttemp_exponent_factor = [edit_reaction.temp_exponent_factor]\n"
+ export += "\tph_exponent_factor = [edit_reaction.ph_exponent_factor]\n"
+ export += "\tthermic_constant = [edit_reaction.thermic_constant]\n"
+ export += "\tH_ion_release = [edit_reaction.H_ion_release]\n"
+ export += "\trate_up_lim = [edit_reaction.rate_up_lim]\n"
+ export += "\tpurity_min = [edit_reaction.purity_min]\n"
+
+ var/dest = "[GLOB.log_directory]/chem_parse.txt"
+ text2file(export, dest)
+ tgui_alert(ui.user, "Saved to [dest]")
+
+ if("eject")
+ if(!target_reagents.total_volume)
+ return
+ if(QDELETED(beaker))
+ beaker = new /obj/item/reagent_containers/cup/beaker/bluespace(src)
+ target_reagents.trans_to(beaker, target_reagents.total_volume)
+ try_put_in_hand(beaker, ui.user)
+ return TRUE
+
+#undef USE_REACTION_TEMPERATURE
+#undef USE_MINIMUM_TEMPERATURE
+#undef USE_USER_TEMPERATURE
+#undef USE_OPTIMAL_TEMPERATURE
+#undef USE_OVERHEAT_TEMPERATURE
+#undef PLAY_NEXT_REACTION
+#undef PLAY_PREVIOUS_REACTION
+#undef PLAY_USER_REACTION
+#undef MAXIMUM_HOLDER_VOLUME
diff --git a/icons/obj/medical/cryogenics.dmi b/icons/obj/medical/cryogenics.dmi
index bd82d4bbace..4dd33af8ce7 100644
Binary files a/icons/obj/medical/cryogenics.dmi and b/icons/obj/medical/cryogenics.dmi differ
diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.tsx b/tgui/packages/tgui/interfaces/ChemDispenser.tsx
index e114d5f5b1d..cb1eedf551f 100644
--- a/tgui/packages/tgui/interfaces/ChemDispenser.tsx
+++ b/tgui/packages/tgui/interfaces/ChemDispenser.tsx
@@ -22,6 +22,10 @@ type DispensableReagent = {
pHCol: string;
};
+type TransferableBeaker = Beaker & {
+ transferAmounts: number[];
+};
+
type Data = {
showpH: BooleanLike;
amount: number;
@@ -31,7 +35,7 @@ type Data = {
recipes: string[];
recordingRecipe: string[];
recipeReagents: string[];
- beaker: Beaker;
+ beaker: TransferableBeaker;
};
export const ChemDispenser = (props) => {
diff --git a/tgui/packages/tgui/interfaces/ChemHeater.tsx b/tgui/packages/tgui/interfaces/ChemHeater.tsx
index 6ac5d757ca1..d9d61c92592 100644
--- a/tgui/packages/tgui/interfaces/ChemHeater.tsx
+++ b/tgui/packages/tgui/interfaces/ChemHeater.tsx
@@ -17,7 +17,7 @@ import { COLORS } from '../constants';
import { Window } from '../layouts';
import { Beaker, BeakerSectionDisplay } from './common/BeakerDisplay';
-type ActiveReaction = {
+export type ActiveReaction = {
name: string;
danger: BooleanLike;
overheat: BooleanLike;
@@ -42,6 +42,144 @@ type Data = {
dispenseVolume: number;
};
+type ReactionDisplayProps = {
+ beaker: Beaker;
+ isFlashing: number;
+ activeReactions: ActiveReaction[];
+ highQualityDisplay: BooleanLike;
+ highDangerDisplay: BooleanLike;
+};
+
+export const ReactionDisplay = (props: ReactionDisplayProps) => {
+ const {
+ beaker,
+ isFlashing,
+ activeReactions,
+ highQualityDisplay,
+ highDangerDisplay,
+ } = props;
+
+ return (
+
+
+ 'pH: ' + round(value, 3)}
+ />
+
+
+ ''}
+ ranges={{
+ red: [-0.22, 1.5],
+ orange: [1.5, 3],
+ yellow: [3, 4.5],
+ olive: [4.5, 5],
+ good: [5, 6],
+ green: [6, 8.5],
+ teal: [8.5, 9.5],
+ blue: [9.5, 11],
+ purple: [11, 12.5],
+ violet: [12.5, 14],
+ }}
+ />
+
+
+ }
+ >
+ {(activeReactions.length === 0 && (
+ No active reactions.
+ )) || (
+
+
+
+ Reaction
+
+
+ {!highQualityDisplay ? 'Status' : 'Reaction quality'}
+
+
+ Target
+
+
+ {activeReactions.map((reaction) => (
+
+
+ {reaction.name}
+
+
+ {!highQualityDisplay ? (
+
+ ) : (
+ ''}
+ ml={5}
+ ranges={{
+ red: [0, reaction.minPure],
+ orange: [reaction.minPure, reaction.inverse],
+ yellow: [reaction.inverse, 0.8],
+ green: [0.8, 1],
+ }}
+ />
+ )}
+
+
+ {highDangerDisplay ? (
+ <>
+ {!!reaction.overheat && (
+
+ )}
+
+ {reaction.targetVol}u
+
+ >
+ ) : (
+
+ {reaction.targetVol}u
+
+ )}
+
+
+ ))}
+
+
+ )}
+
+ );
+};
+
export const ChemHeater = (props) => {
const { act, data } = useBackend();
const {
@@ -208,125 +346,13 @@ export const ChemHeater = (props) => {
{!!isBeakerLoaded && (
-
-
- 'pH: ' + round(value, 3)}
- />
-
-
- ''}
- ranges={{
- red: [-0.22, 1.5],
- orange: [1.5, 3],
- yellow: [3, 4.5],
- olive: [4.5, 5],
- good: [5, 6],
- green: [6, 8.5],
- teal: [8.5, 9.5],
- blue: [9.5, 11],
- purple: [11, 12.5],
- violet: [12.5, 14],
- }}
- />
-
-
- }
- >
- {(activeReactions.length === 0 && (
- No active reactions.
- )) || (
-
-
-
- Reaction
-
-
- {upgradeLevel < 4 ? 'Status' : 'Reaction quality'}
-
-
- Target
-
-
- {activeReactions.map((reaction) => (
-
-
- {reaction.name}
-
-
- {(upgradeLevel < 4 && (
-
- )) || (
- ''}
- ml={5}
- ranges={{
- red: [0, reaction.minPure],
- orange: [reaction.minPure, reaction.inverse],
- yellow: [reaction.inverse, 0.8],
- green: [0.8, 1],
- }}
- />
- )}
-
-
- {(upgradeLevel > 2 && (
- <>
- {!!reaction.overheat && (
-
- )}
-
- {reaction.targetVol}u
-
- >
- )) || (
-
- {reaction.targetVol}u
-
- )}
-
-
- ))}
-
-
- )}
-
+ = 4}
+ highDangerDisplay={upgradeLevel >= 2}
+ />
)}
diff --git a/tgui/packages/tgui/interfaces/ChemRecipeDebug.jsx b/tgui/packages/tgui/interfaces/ChemRecipeDebug.jsx
deleted file mode 100644
index ed47c6f95b8..00000000000
--- a/tgui/packages/tgui/interfaces/ChemRecipeDebug.jsx
+++ /dev/null
@@ -1,326 +0,0 @@
-import { round } from 'common/math';
-
-import { useBackend } from '../backend';
-import {
- AnimatedNumber,
- Box,
- Button,
- Flex,
- LabeledList,
- NumberInput,
- ProgressBar,
- RoundGauge,
- Section,
- Table,
-} from '../components';
-import { Window } from '../layouts';
-import { BeakerContents } from './common/BeakerContents';
-
-export const ChemRecipeDebug = (props) => {
- const { act, data } = useBackend();
- const {
- targetTemp,
- isActive,
- isFlashing,
- currentTemp,
- currentpH,
- forcepH,
- forceTemp,
- targetVol,
- targatpH,
- processing,
- processAll,
- index,
- endIndex,
- beakerSpawn,
- minTemp,
- editRecipeName,
- editRecipeCold,
- editRecipe = [],
- chamberContents = [],
- activeReactions = [],
- queuedReactions = [],
- } = data;
- return (
-
-
-
-
-
-
-
- act('setEdit')}
- />
-
- {!!editRecipe && (
- <>
-
-
- act('updateVar', {
- type: entry.name,
- target: value,
- })
- }
- />
-
- {editRecipe.map((entry) => (
-
-
- act('updateVar', {
- type: entry.name,
- target: value,
- })
- }
- />
-
- ))}
- >
- )}
-
- act('export')}
- />
-
-
-
-
-
- 'pH: ' + round(value, 3)}
- />
-
-
- ''}
- ranges={{
- red: [-0.22, 1.5],
- orange: [1.5, 3],
- yellow: [3, 4.5],
- olive: [4.5, 5],
- good: [5, 6],
- green: [6, 8.5],
- teal: [8.5, 9.5],
- blue: [9.5, 11],
- purple: [11, 12.5],
- violet: [12.5, 14],
- }}
- />
-
-
- }
- >
- {(activeReactions.length === 0 && (
- No active reactions.
- )) || (
-
-
-
- Reaction
-
-
- {'Reaction quality'}
-
-
- Target
-
-
- {activeReactions &&
- activeReactions.map((reaction) => (
-
-
- {reaction.name}
-
-
- ''}
- ml={5}
- ranges={{
- red: [0, reaction.minPure],
- orange: [reaction.minPure, reaction.inverse],
- yellow: [reaction.inverse, 0.8],
- green: [0.8, 1],
- }}
- />
-
-
-
- {reaction.targetVol}u
-
-
-
- ))}
-
-
- )}
-
- {isActive ? 'Reacting' : 'Waiting'}}
- >
- {(chamberContents.length && (
-
- )) || Nothing}
-
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx b/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx
new file mode 100644
index 00000000000..c108966bbb7
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ChemRecipeDebug.tsx
@@ -0,0 +1,437 @@
+import { BooleanLike } from 'common/react';
+import { useState } from 'react';
+
+import { useBackend } from '../backend';
+import {
+ Box,
+ Button,
+ Dropdown,
+ LabeledList,
+ NumberInput,
+ Section,
+ Stack,
+ Tabs,
+} from '../components';
+import { Window } from '../layouts';
+import { ActiveReaction, ReactionDisplay } from './ChemHeater';
+import { Beaker, BeakerSectionDisplay } from './common/BeakerDisplay';
+
+const TEMP_MODES = [
+ 'Reaction Temp',
+ 'Forced Temp',
+ 'Minimum Temp',
+ 'Optimal Temp',
+ 'Overheat Temp',
+];
+const REACTION_MODES = ['Next Reaction', 'Previous Reaction', 'Pick Reaction'];
+const REACTION_VARS = [
+ 'Required Temp',
+ 'Optimal Temp',
+ 'Overheat Temp',
+ 'Optimal Min Ph',
+ 'Optimal Max Ph',
+ 'Ph Range',
+ 'Temp Exp Factor',
+ 'Ph Exp Factor',
+ 'Thermic Constant',
+ 'H Ion Release',
+ 'Rate Up Limit',
+ 'Purity Min',
+];
+
+type BeakerDebug = Beaker & {
+ currentTemp: number;
+ purity: number;
+};
+
+type Reaction = {
+ name: string;
+ editVar: string;
+ editValue: number;
+};
+
+type Data = {
+ forced_temp: number;
+ temp_mode: number;
+ forced_ph: number;
+ use_forced_ph: BooleanLike;
+ forced_purity: number;
+ use_forced_purity: number;
+ volume_multiplier: number;
+ isReacting: BooleanLike;
+ current_reaction_name: string;
+ current_reaction_mode: number;
+ beaker: BeakerDebug;
+ isFlashing: number;
+ activeReactions: ActiveReaction[];
+ editReaction: Reaction;
+};
+
+export const ChemRecipeDebug = (props) => {
+ const { act, data } = useBackend();
+ const [controlState, setControlState] = useState('Environment');
+ const {
+ forced_temp,
+ temp_mode,
+ forced_ph,
+ use_forced_ph,
+ forced_purity,
+ use_forced_purity,
+ volume_multiplier,
+ isReacting,
+ current_reaction_name,
+ current_reaction_mode,
+ beaker,
+ isFlashing,
+ activeReactions,
+ editReaction,
+ } = data;
+ return (
+
+
+
+
+ setControlState('Environment')}
+ >
+ Environment
+
+ setControlState('Reactions')}
+ >
+ Reactions
+
+ setControlState('Editing')}
+ >
+ Edit Reactions
+
+
+ {controlState === 'Environment' && (
+
+
+
+
+
+
+ act('forced_temp', {
+ target: value,
+ })
+ }
+ />
+
+
+
+
+
+
+ Temp Mode:
+
+ }
+ >
+
+ act('temp_mode', {
+ target: value,
+ })
+ }
+ />
+
+
+
+
+
+
+
+ PH:}>
+
+ act('forced_ph', {
+ target: value,
+ })
+ }
+ />
+
+
+
+
+
+
+ act('toggle_forced_ph')}
+ >
+ {use_forced_ph ? 'Disable' : 'Enable'}
+
+
+
+
+
+
+
+
+ Purity:}>
+
+ act('forced_purity', {
+ target: value,
+ })
+ }
+ />
+
+
+
+
+
+
+ act('toggle_forced_purity')}
+ >
+ {use_forced_purity ? 'Disable' : 'Enable'}
+
+
+
+
+
+
+
+
+
+
+ act('volume_multiplier', {
+ target: value,
+ })
+ }
+ />
+
+
+
+
+
+ )}
+ {controlState === 'Reactions' && (
+
+
+
+
+
+ act('pick_reaction')}
+ >
+ Select Reaction
+
+
+
+
+
+
+
+ {current_reaction_name}
+
+
+
+
+
+
+ Direction:
+
+ }
+ >
+
+ act('reaction_mode', {
+ target: value,
+ })
+ }
+ />
+
+
+
+
+
+ Process:}>
+ act('start_reaction')}
+ >
+ Play
+
+
+
+
+
+
+ )}
+ {controlState === 'Editing' && (
+
+
+
+
+
+ act('edit_reaction')}
+ >
+ {editReaction?.name || 'Edit Reaction'}
+
+
+
+
+
+
+
+ Param:
+
+ }
+ >
+
+ act('edit_var', { target: value })
+ }
+ disabled={editReaction === null}
+ />
+
+
+
+
+
+ Value:}>
+
+ act('edit_value', {
+ target: value,
+ })
+ }
+ />
+ act('reset_value')}
+ />
+
+
+
+
+
+ Export:}>
+ act('export')}
+ disabled={editReaction === null}
+ >
+ Export
+
+
+
+
+
+
+ )}
+
+ {beaker && (
+
+
+
+ {beaker.currentTemp}
+
+
+ {beaker.purity}
+
+
+
+ )}
+ {beaker && (
+
+ )}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/Cryo.jsx b/tgui/packages/tgui/interfaces/Cryo.jsx
deleted file mode 100644
index de547e93fba..00000000000
--- a/tgui/packages/tgui/interfaces/Cryo.jsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import { useBackend } from '../backend';
-import {
- AnimatedNumber,
- Button,
- LabeledList,
- ProgressBar,
- Section,
-} from '../components';
-import { Window } from '../layouts';
-import { BeakerContents } from './common/BeakerContents';
-
-const damageTypes = [
- {
- label: 'Brute',
- type: 'bruteLoss',
- },
- {
- label: 'Respiratory',
- type: 'oxyLoss',
- },
- {
- label: 'Toxin',
- type: 'toxLoss',
- },
- {
- label: 'Burn',
- type: 'fireLoss',
- },
-];
-
-export const Cryo = () => {
- return (
-
-
-
-
-
- );
-};
-
-const CryoContent = (props) => {
- const { act, data } = useBackend();
- return (
- <>
-
-
-
- {data.occupant.name || 'No Occupant'}
-
- {!!data.hasOccupant && (
- <>
-
- {data.occupant.stat}
-
-
-
- {' K'}
-
-
- 0 ? 'good' : 'average'}
- >
-
-
-
- {damageTypes.map((damageType) => (
-
-
-
-
-
- ))}
- >
- )}
-
-
-
-
-
- act('power')}
- color={data.isOperating && 'green'}
- >
- {data.isOperating ? 'On' : 'Off'}
-
-
-
- K
-
-
- act('door')}
- content={data.isOpen ? 'Open' : 'Closed'}
- />
- act('autoeject')}
- content={data.autoEject ? 'Auto' : 'Manual'}
- />
-
-
-
- act('ejectbeaker')}
- content="Eject"
- />
- }
- >
-
-
- >
- );
-};
diff --git a/tgui/packages/tgui/interfaces/Cryo.tsx b/tgui/packages/tgui/interfaces/Cryo.tsx
new file mode 100644
index 00000000000..2999f4c04d8
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/Cryo.tsx
@@ -0,0 +1,150 @@
+import { round } from 'common/math';
+import { BooleanLike } from 'common/react';
+
+import { useBackend } from '../backend';
+import {
+ AnimatedNumber,
+ Button,
+ LabeledList,
+ ProgressBar,
+ Section,
+} from '../components';
+import { Window } from '../layouts';
+import { Beaker, BeakerSectionDisplay } from './common/BeakerDisplay';
+
+const damageTypes = [
+ {
+ label: 'Brute',
+ type: 'bruteLoss',
+ },
+ {
+ label: 'Respiratory',
+ type: 'oxyLoss',
+ },
+ {
+ label: 'Toxin',
+ type: 'toxLoss',
+ },
+ {
+ label: 'Burn',
+ type: 'fireLoss',
+ },
+] as const;
+
+const stat_to_color = {
+ Dead: 'bad',
+ Conscious: 'bad',
+ Unconscious: 'good',
+} as const;
+
+type Occupant = {
+ name: string;
+ stat: string;
+ bodyTemperature: number;
+ health: number;
+ maxHealth: number;
+ bruteLoss: number;
+ oxyLoss: number;
+ toxLoss: number;
+ fireLoss: number;
+};
+
+type Data = {
+ isOperating: BooleanLike;
+ isOpen: BooleanLike;
+ autoEject: BooleanLike;
+ occupant: Occupant;
+ T0C: number;
+ cellTemperature: number;
+ beaker: Beaker;
+};
+
+export const Cryo = () => {
+ const { act, data } = useBackend();
+ const { occupant, isOperating, isOpen } = data;
+
+ return (
+
+
+
+
+
+ {occupant?.name || 'No Occupant'}
+
+ {!!occupant && (
+ <>
+
+ {occupant.stat}
+
+
+
+ {' K'}
+
+
+ 0 ? 'good' : 'average'}
+ >
+
+
+
+ {damageTypes.map((damageType) => (
+
+
+
+
+
+ ))}
+ >
+ )}
+
+
+
+
+
+ act('power')}
+ color={isOperating && 'green'}
+ >
+ {isOperating ? 'On' : 'Off'}
+
+
+
+ K
+
+
+ act('door')}
+ >
+ {isOpen ? 'Open' : 'Closed'}
+
+ act('autoeject')}
+ >
+ {data.autoEject ? 'Auto' : 'Manual'}
+
+
+
+
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/interfaces/PortableChemMixer.tsx b/tgui/packages/tgui/interfaces/PortableChemMixer.tsx
index 15cbb56ba92..fc0b4cbb95e 100644
--- a/tgui/packages/tgui/interfaces/PortableChemMixer.tsx
+++ b/tgui/packages/tgui/interfaces/PortableChemMixer.tsx
@@ -12,10 +12,14 @@ type DispensableReagent = {
pH: number;
};
+type TransferableBeaker = Beaker & {
+ transferAmounts: number[];
+};
+
type Data = {
amount: number;
chemicals: DispensableReagent[];
- beaker: Beaker;
+ beaker: TransferableBeaker;
};
export const PortableChemMixer = (props) => {
diff --git a/tgui/packages/tgui/interfaces/common/BeakerContents.jsx b/tgui/packages/tgui/interfaces/common/BeakerContents.jsx
deleted file mode 100644
index b330f956079..00000000000
--- a/tgui/packages/tgui/interfaces/common/BeakerContents.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { AnimatedNumber, Box } from '../../components';
-
-export const BeakerContents = (props) => {
- const { beakerLoaded, beakerContents } = props;
- return (
-
- {(!beakerLoaded && No beaker loaded.) ||
- (beakerContents.length === 0 && (
- Beaker is empty.
- ))}
- {beakerContents.map((chemical) => (
-
-
- {' units of ' + chemical.name}
-
- ))}
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx b/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx
index 5c057755c5c..d567269d907 100644
--- a/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx
+++ b/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx
@@ -15,7 +15,6 @@ type BeakerReagent = {
export type Beaker = {
maxVolume: number;
- transferAmounts: number[];
pH: number;
currentVolume: number;
contents: BeakerReagent[];