diff --git a/code/datums/components/crafting/chemistry.dm b/code/datums/components/crafting/chemistry.dm
index 62504103eca..fc4d833be3f 100644
--- a/code/datums/components/crafting/chemistry.dm
+++ b/code/datums/components/crafting/chemistry.dm
@@ -120,6 +120,20 @@
)
category = CAT_CHEMISTRY
+
+/datum/crafting_recipe/chem_separator
+ name = "chemical separator"
+ result = /obj/structure/chem_separator
+ tool_behaviors = list(TOOL_WELDER)
+ time = 5 SECONDS
+ reqs = list(
+ /obj/item/stack/sheet/mineral/wood = 1,
+ /obj/item/stack/sheet/glass = 1,
+ /obj/item/burner = 1,
+ /obj/item/thermometer = 1,
+ )
+ category = CAT_CHEMISTRY
+
/datum/crafting_recipe/improvised_chem_heater
name = "Improvised chem heater"
result = /obj/machinery/space_heater/improvised_chem_heater
diff --git a/code/modules/reagents/chemistry/colors.dm b/code/modules/reagents/chemistry/colors.dm
index 6b260210c9e..ae57faa481c 100644
--- a/code/modules/reagents/chemistry/colors.dm
+++ b/code/modules/reagents/chemistry/colors.dm
@@ -19,3 +19,19 @@
mixcolor = BlendRGB(R.color, mixcolor, vol_temp/vol_counter)
return mixcolor
+
+/proc/reagent_threshold_overlay(datum/reagents/reagents, fill_icon, fill_prefix, list/fill_icon_thresholds)
+ RETURN_TYPE(/mutable_appearance)
+
+ var/threshold = null
+ for(var/i in 1 to fill_icon_thresholds.len)
+ if(ROUND_UP(100 * reagents.total_volume / reagents.maximum_volume) >= fill_icon_thresholds[i])
+ threshold = i
+
+ if(threshold)
+ var/fill_name = "[fill_prefix][fill_icon_thresholds[threshold]]"
+ var/mutable_appearance/filling = mutable_appearance(fill_icon, fill_name)
+ filling.color = mix_color_from_reagents(reagents.reagent_list)
+ return filling
+
+ return null
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index a44b60c44f8..5ea84222447 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -92,7 +92,7 @@
/obj/machinery/chem_master/update_appearance(updates)
. = ..()
- if(panel_open || (machine_stat & (NOPOWER|BROKEN)))
+ if(panel_open || !is_operational)
set_light(0)
else
set_light(1, 1, "#fffb00")
@@ -112,7 +112,7 @@
. += mutable_appearance(icon, base_icon_state + "_overlay_extruder")
// Screen overlay
- if(!panel_open && !(machine_stat & (NOPOWER | BROKEN)))
+ if(!panel_open && is_operational)
var/screen_overlay = base_icon_state + "_overlay_screen"
if(is_printing)
screen_overlay += "_active"
@@ -123,15 +123,9 @@
// Buffer reagents overlay
if(reagents.total_volume)
- var/threshold = null
var/static/list/fill_icon_thresholds = list(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
- for(var/i in 1 to fill_icon_thresholds.len)
- if(ROUND_UP(100 * (reagents.total_volume / reagents.maximum_volume)) >= fill_icon_thresholds[i])
- threshold = i
- if(threshold)
- var/fill_name = "chemmaster[fill_icon_thresholds[threshold]]"
- var/mutable_appearance/filling = mutable_appearance('icons/obj/medical/reagent_fillings.dmi', fill_name)
- filling.color = mix_color_from_reagents(reagents.reagent_list)
+ var/mutable_appearance/filling = reagent_threshold_overlay(reagents, 'icons/obj/medical/reagent_fillings.dmi', "chemmaster", fill_icon_thresholds)
+ if(!isnull(filling))
. += filling
/obj/machinery/chem_master/Exited(atom/movable/gone, direction)
diff --git a/code/modules/reagents/chemistry/machinery/chem_separator.dm b/code/modules/reagents/chemistry/machinery/chem_separator.dm
index 13be8d6554f..ac964b9f994 100644
--- a/code/modules/reagents/chemistry/machinery/chem_separator.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_separator.dm
@@ -1,245 +1,439 @@
+///The maximum number of settings on a burner knob
+#define MAX_BURNER_KNOB_SETTINGS 10
+
/obj/structure/chem_separator
name = "distillation apparatus"
desc = "A device that performs chemical separation by distillation."
icon = 'icons/obj/medical/chemical.dmi'
icon_state = "separator"
light_power = 1
- var/fill_icon = 'icons/obj/medical/reagent_fillings.dmi'
- var/fill_icon_state = "separator"
- /// Icons for different percentages of beaker/separator reagent volumes
- var/list/fill_icon_thresholds = list(1,30,80)
- /// The temperature thresholds used for thermometer icon update, in Celsius
- var/list/temperature_icon_thresholds = list(0,50,100)
- /// Whether the burner is currently on and the mixture is heating up
- var/burning = FALSE
- /// Whether the mixture is above the required temperature and the separation is in process
+
+ ///Is the mixture currently boiling
var/boiling = FALSE
+ /// Has the sound loop animation started
+ var/loop_started = FALSE
/// Sound during separation
var/datum/looping_sound/boiling/soundloop
- /// Minimal mixture temperature for separation
- var/required_temp = T0C + 100
- /// Mixture heating speed in degrees per second for full container
- var/heating_rate = 5
- /// Separation speed in units per second
- var/distillation_rate = 5
- /// Reagent container for the vapor of the separating reagent
- var/datum/reagents/condenser
- /// The reagent chosen for separation
- var/datum/reagent/separating_reagent
- /// Reagent container for condensate or separator filling
- var/obj/item/reagent_containers/beaker
+ /// The container for transferring distilled reagents into
+ var/obj/item/reagent_containers/distilled_container
+ /// The container for holding the fuel source for the bunset burner
+ var/obj/item/reagent_containers/fuel_container
+ /// Is the bunset burner currenrly switched on/off
+ var/burner_on = FALSE
+ /// Do we have a condenser installed for forced cooling
+ var/condenser_installed = FALSE
+ /// Is the condenser on
+ var/condenser_on = FALSE
+ /// Knob setting on the burner
+ var/burner_knob = 1
/obj/structure/chem_separator/Initialize(mapload)
. = ..()
- create_reagents(300, TRANSPARENT | INJECTABLE)
- condenser = new()
- soundloop = new(src, boiling)
+ create_reagents(100, TRANSPARENT | INJECTABLE)
+ soundloop = new(src)
+ register_context()
/obj/structure/chem_separator/Destroy()
- if(burning)
- STOP_PROCESSING(SSobj, src)
- if(beaker)
- QDEL_NULL(beaker)
+ QDEL_NULL(distilled_container)
+ QDEL_NULL(fuel_container)
QDEL_NULL(soundloop)
return ..()
+/obj/structure/chem_separator/atom_deconstruct(disassembled)
+ var/atom/drop = drop_location()
+
+ new /obj/item/stack/sheet/mineral/wood(drop, 1)
+
+ new /obj/item/thermometer(drop)
+
+ new /obj/item/burner(drop)
+
+ if(condenser_installed)
+ new /obj/item/assembly/igniter/condenser(drop)
+
+ if(!QDELETED(distilled_container))
+ distilled_container.forceMove(drop)
+
+ if(!QDELETED(fuel_container))
+ fuel_container.forceMove(drop)
+
/obj/structure/chem_separator/Exited(atom/movable/gone, direction)
. = ..()
- if(gone == beaker)
- beaker = null
- update_appearance(UPDATE_ICON)
+ if(distilled_container == gone)
+ distilled_container = null
+ update_appearance(UPDATE_OVERLAYS)
+ if(fuel_container == gone)
+ toggle_burner(FALSE)
+ fuel_container = null
+ update_appearance(UPDATE_OVERLAYS)
+
+/obj/structure/chem_separator/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = NONE
+ if(isnull(held_item))
+ if(!QDELETED(distilled_container))
+ context[SCREENTIP_CONTEXT_LMB] = "Remove beaker"
+ . = CONTEXTUAL_SCREENTIP_SET
+ if(!QDELETED(fuel_container))
+ context[SCREENTIP_CONTEXT_RMB] = "Remove fuel"
+ . = CONTEXTUAL_SCREENTIP_SET
+ if(burner_on)
+ context[SCREENTIP_CONTEXT_ALT_LMB] = "Off burner"
+ . = CONTEXTUAL_SCREENTIP_SET
+ return
+
+ if(!condenser_installed && istype(held_item, /obj/item/assembly/igniter/condenser))
+ context[SCREENTIP_CONTEXT_LMB] = "Installer cooler"
+ return CONTEXTUAL_SCREENTIP_SET
+
+ if(is_reagent_container(held_item) && held_item.is_open_container())
+ if(QDELETED(distilled_container))
+ context[SCREENTIP_CONTEXT_LMB] = "[QDELETED(distilled_container) ? "Insert" : "Replace"] beaker"
+ return CONTEXTUAL_SCREENTIP_SET
+ if(QDELETED(fuel_container))
+ context[SCREENTIP_CONTEXT_RMB] = "[QDELETED(fuel_container) ? "Insert" : "Replace"] fuel"
+ return CONTEXTUAL_SCREENTIP_SET
+
+ if(held_item.tool_behaviour == TOOL_CROWBAR)
+ context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
+ return CONTEXTUAL_SCREENTIP_SET
+
+/obj/structure/chem_separator/examine(mob/user)
+ . = ..()
+
+ if(reagents.total_volume)
+ . += span_notice("The distilation flask reads [reagents.total_volume]/[reagents.maximum_volume]u.")
+ if(!QDELETED(distilled_container))
+ . += span_notice("The distilation beaker reads [distilled_container.reagents.total_volume]/[distilled_container.reagents.maximum_volume]u.")
+ . += span_notice("Remove beaker with [EXAMINE_HINT("LMB")].")
+ else
+ . += span_warning("Its missing a distilation container, insert with [EXAMINE_HINT("LMB")]")
+ if(!QDELETED(fuel_container))
+ . += span_notice("The burner fuel container reads [fuel_container.reagents.total_volume]/[fuel_container.reagents.maximum_volume]u.")
+ . += span_notice("Remove fuel with [EXAMINE_HINT("RMB")].")
+ else
+ . += span_warning("Its missing a beaker containing fuel for the burner, insert with [EXAMINE_HINT("RMB")]")
+ if(burner_on)
+ . += span_notice("Off burner with [EXAMINE_HINT("ALT LMB")].")
+ else
+ . += span_notice("You can start a flame with an combustible device.")
+
+ if(condenser_installed)
+ . += span_notice("The in-built condenser can facilitate faster cooling but consumes fuel.")
+ else
+ . += span_notice("You could install a [EXAMINE_HINT("condenser")] for fater cooling.")
+
+ . += span_notice("You can [EXAMINE_HINT("examine more")] to see reagent boiling points & fuel properties.")
+ . += span_notice("The whole aparatus can be [EXAMINE_HINT("pried")] apart.")
+
+/obj/structure/chem_separator/examine_more(mob/user)
+ . = ..()
+
+ . += span_notice("For burner fuel Plasma > Oil > Welding Fuel = Oxygen > Ethanol > Monkey Energy")
+
+ . += span_notice("Upon cross examining the flasks reagents contents with its chart you see the boiling points of each reagent present.")
+ for(var/datum/reagent/reg as anything in reagents.reagent_list)
+ . += span_notice("[reg.name] [get_boiling_point(reg)]K")
/obj/structure/chem_separator/update_overlays()
. = ..()
- set_light(burning ? light_power : 0)
- // Burner overlay
- if(burning)
- . += mutable_appearance(icon, "[icon_state]_burn")
- . += emissive_appearance(icon, "[icon_state]_burn", src)
- // Separator reagents overlay
+
+ //burner overlays
+ if(burner_on)
+ . += mutable_appearance('icons/obj/medical/chemical.dmi', "separator_burn")
+ . += emissive_appearance('icons/obj/medical/chemical.dmi', "separator_burn", src)
+
+ var/static/list/fill_icon_thresholds = list(1, 30, 80)
+
+ //distilation flask overlays
if(reagents.total_volume)
- var/threshold = null
- for(var/i in 1 to fill_icon_thresholds.len)
- if(ROUND_UP(100 * reagents.total_volume / reagents.maximum_volume) >= fill_icon_thresholds[i])
- threshold = i
- if(threshold)
- var/fill_name = "[fill_icon_state]_m_[fill_icon_thresholds[threshold]]"
- var/mutable_appearance/filling = mutable_appearance(fill_icon, fill_name)
- filling.color = mix_color_from_reagents(reagents.reagent_list)
- . += filling
- // Beaker overlay
- if(beaker)
- . += "[icon_state]_beaker"
- // Beaker reagents overlay
- if(beaker.reagents.total_volume)
- var/threshold = null
- for(var/i in 1 to fill_icon_thresholds.len)
- if(ROUND_UP(100 * beaker.reagents.total_volume / beaker.reagents.maximum_volume) >= fill_icon_thresholds[i])
- threshold = i
- if(threshold)
- var/fill_name = "[fill_icon_state]_b_[fill_icon_thresholds[threshold]]"
- var/mutable_appearance/filling = mutable_appearance(fill_icon, fill_name)
- filling.color = mix_color_from_reagents(beaker.reagents.reagent_list)
- . += filling
- // Dripping overlay
- if(boiling)
- var/mutable_appearance/filling = mutable_appearance(fill_icon, "separator_dripping")
- filling.color = separating_reagent.color
- . += filling
- // Thermometer overlay
+ var/mutable_appearance/overlay = reagent_threshold_overlay(reagents, 'icons/obj/medical/reagent_fillings.dmi', "separator_m_", fill_icon_thresholds)
+ if(!isnull(overlay))
+ . += overlay
+
+ //dripping overlay
+ if(boiling)
+ var/mutable_appearance/filling = mutable_appearance('icons/obj/medical/reagent_fillings.dmi', "separator_dripping")
+ filling.color = mix_color_from_reagents(reagents.reagent_list)
+ . += filling
+
+ //distilation beaker overlays
+ if(!QDELETED(distilled_container))
+ . += "separator_beaker"
+ var/mutable_appearance/overlay = reagent_threshold_overlay(distilled_container.reagents, 'icons/obj/medical/reagent_fillings.dmi', "separator_b_", fill_icon_thresholds)
+ if(!isnull(overlay))
+ . += overlay
+
+ //thermometer overlay
+ var/static/list/temperature_icon_thresholds = list(0, 50, 100)
var/threshold = null
for(var/i in 1 to temperature_icon_thresholds.len)
if(ROUND_UP(reagents.chem_temp - T0C) >= temperature_icon_thresholds[i])
threshold = i
if(threshold)
- var/fill_name = "[icon_state]_temp_[temperature_icon_thresholds[threshold]]"
- var/mutable_appearance/filling = mutable_appearance(icon_state, fill_name)
+ var/fill_name = "separator_temp_[temperature_icon_thresholds[threshold]]"
+ var/mutable_appearance/filling = mutable_appearance('icons/obj/medical/chemical.dmi', fill_name)
. += filling
-/// Checks whether the item can ignite the separator
-/obj/structure/chem_separator/proc/ignite_with(obj/item/object, mob/living/user)
- var/ignition_message = object.ignition_effect(src, user)
- if(!ignition_message)
- return FALSE
- user.visible_message(ignition_message)
- fire_act(object.get_temperature())
- return TRUE
-
-/obj/structure/chem_separator/attackby(obj/item/item, mob/user, params)
- if(ignite_with(item, user))
- return TRUE // no afterattack
- if(is_reagent_container(item) && !(item.item_flags & ABSTRACT) && item.is_open_container())
- var/obj/item/reagent_containers/new_beaker = item
- if(!user.transferItemToLoc(new_beaker, src))
- return FALSE
- replace_beaker(user, new_beaker)
- balloon_alert(user, "added beaker")
- update_appearance(UPDATE_ICON)
- return TRUE // no afterattack
- return ..()
+/**
+ * Computes the boiling point of the reagent based on its mass. heaiver reagents obviously needs higher temps
+ * Arguments
+ *
+ * * datum/reagent/reg - the reagent whos boiling point we are trying to compute
+ */
+/obj/structure/chem_separator/proc/get_boiling_point(datum/reagent/reg)
+ PRIVATE_PROC(TRUE)
-/// Insert, replace or eject the container depending on the state and parameters
-/obj/structure/chem_separator/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
- if(!user)
- CRASH("[user] ([user?.type]) is not a living, but is trying to replace beaker")
- if(beaker)
- if(burning)
- stop()
- if(!issilicon(user) && in_range(src, user))
- user.put_in_hands(beaker)
- playsound(src, 'sound/items/handling/drinkglass_pickup.ogg', PICKUP_SOUND_VOLUME, ignore_walls = FALSE)
- else
- beaker.forceMove(drop_location())
- beaker = null
- if(new_beaker)
- beaker = new_beaker
- playsound(src, 'sound/items/handling/drinkglass_drop.ogg', DROP_SOUND_VOLUME, ignore_walls = FALSE)
- update_appearance(UPDATE_ICON)
- return TRUE
+ //use the constant mass set on init
+ reg = GLOB.chemical_reagents_list[reg.type]
+
+ //reagents masses can vary between 10->800
+ var/normalized_coord = (reg.mass - 10) / 790
+
+ //return a boiling point anywhere between 400->900 k. Change if you want more varity
+ return 400 + (500 * normalized_coord)
+
+/**
+ * Computes the coefficient of burning(the ability of the reagent mixture to burn) of the burner fuel container
+ * reagents can affect the intensity of the flame in different ways. A -ve value the mixture can not combust
+ * whereas a +ve value(<= 1) means the flame can burn at maximum efficiency
+ */
+/obj/structure/chem_separator/proc/get_ignition_coefficient()
+ PRIVATE_PROC(TRUE)
+
+ if(QDELETED(fuel_container))
+ return 0
+
+ //map of reagents & how much burning potential they all have
+ var/static/list/reagent_coefficients = list(
+ /datum/reagent/toxin/plasma = 1,
+ /datum/reagent/fuel/oil = 0.9,
+ /datum/reagent/fuel = 0.8,
+ /datum/reagent/oxygen = 0.8,
+ /datum/reagent/consumable/ethanol = 0.7,
+ /datum/reagent/consumable/monkey_energy = 0.6,
+ /datum/reagent/water = - 0.7
+ )
+
+ var/total_coefficient = 0
+ for(var/datum/reagent/reg as anything in fuel_container.reagents.reagent_list)
+ var/coefficient = -1 //any fuel that is not on the list acts as an inhibitor
+ for(var/datum/reagent/fuel as anything in reagent_coefficients)
+ if(istype(reg, fuel))
+ coefficient = reagent_coefficients[fuel]
+ break
+ total_coefficient += coefficient
+
+ return clamp(total_coefficient, 0, 1)
+
+/**
+ * Toggles the burner on(only for a good fuel composition) or off
+ * Arguments
+ *
+ * * state - on or off
+ */
+/obj/structure/chem_separator/proc/toggle_burner(state)
+ PRIVATE_PROC(TRUE)
+
+ if(!state)
+ burner_on = FALSE
+ set_light(0)
+ else
+ if(!get_ignition_coefficient()) //no proper fuel
+ return
+ if(!reagents.total_volume) //no reagents to distill
+ return
+ if(QDELETED(distilled_container)) //no beaker to receive distilled reagents
+ return
+ burner_on = TRUE
+ set_light(ROUND_UP(2 * (burner_knob / 5)))
+ update_appearance(UPDATE_OVERLAYS)
/obj/structure/chem_separator/fire_act(exposed_temperature, exposed_volume)
- if(burning)
- return ..()
- start()
+ toggle_burner(TRUE)
/obj/structure/chem_separator/extinguish()
. = ..()
- if(burning)
- stop()
+ toggle_burner(FALSE)
-/// Ignite the burner to start the separation process
-/obj/structure/chem_separator/proc/start()
- if(!beaker)
- return
- if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume)
- return
- if(!reagents.total_volume)
- return
- var/list/reagents_sorted = reagents.reagent_list.Copy()
- reagents_sorted = sort_list(reagents_sorted, GLOBAL_PROC_REF(cmp_reagents_asc))
- separating_reagent = reagents_sorted[1]
- burning = TRUE
- update_appearance(UPDATE_ICON)
- START_PROCESSING(SSobj, src)
-
-/// Extinguish the burner to stop the separation process
-/obj/structure/chem_separator/proc/stop()
- separating_reagent = null
- burning = FALSE
- if(boiling)
- boiling = FALSE
- condenser.trans_to(reagents, condenser.total_volume)
- soundloop.stop()
- update_appearance(UPDATE_ICON)
- STOP_PROCESSING(SSobj, src)
+/obj/structure/chem_separator/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
+ . = NONE
+ if(user.combat_mode || tool.item_flags & ABSTRACT || tool.flags_1 & HOLOGRAM_1 || !user.can_perform_action(src, ALLOW_SILICON_REACH))
+ return ITEM_INTERACT_SKIP_TO_ATTACK
-/// Fill internal storage with reagents from the container
-/obj/structure/chem_separator/proc/load()
- if(burning)
- return
- if(!beaker?.reagents.total_volume)
- return
- if(reagents.total_volume >= reagents.maximum_volume)
- return
- beaker.reagents.trans_to(reagents, beaker.reagents.total_volume)
- update_appearance(UPDATE_ICON)
+ ///Add the distilation flask
+ if(is_reagent_container(tool) && tool.is_open_container())
+ //transfer old container
+ if(!QDELETED(distilled_container))
+ user.put_in_hands(distilled_container)
-/// Drain internal reagents into the container
-/obj/structure/chem_separator/proc/unload()
- if(burning)
- return
- if(!reagents.total_volume)
- return
- if(!beaker)
- return
- if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume)
- return
- reagents.trans_to(beaker.reagents, reagents.total_volume)
- update_appearance(UPDATE_ICON)
-
-/// Check whether the separation can process
-/obj/structure/chem_separator/proc/can_process(datum/gas_mixture/air)
- if(!burning)
- return FALSE
- if(!air || !air.has_gas(/datum/gas/oxygen, 1))
- return FALSE
- if(air.temperature > required_temp) // Too hot to condense
- return FALSE
- if(!beaker)
- return FALSE
- if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume)
- return FALSE
- if(!reagents.get_reagent_amount(separating_reagent.type))
- return FALSE
- return TRUE
+ //add new container
+ if(!user.transferItemToLoc(tool, src))
+ to_chat(user, span_warning("[tool] is stuck in your hand."))
+ return ITEM_INTERACT_BLOCKING
+ distilled_container = tool
+
+ START_PROCESSING(SSobj, src)
+ balloon_alert(user, "distillation container added.")
+
+ ui_interact(user)
+ update_appearance(UPDATE_OVERLAYS)
+ return ITEM_INTERACT_SUCCESS
+ else if(istype(tool, /obj/item/assembly/igniter/condenser))
+ if(!user.temporarilyRemoveItemFromInventory(tool))
+ to_chat(user, span_warning("[tool] is stuck in your hand."))
+ return ITEM_INTERACT_BLOCKING
+ condenser_installed = TRUE
+ update_static_data_for_all_viewers()
+ qdel(tool)
+ balloon_alert(user, "condenser installed.")
+ return ITEM_INTERACT_SUCCESS
+
+ ///Try & ignite the bunset burner with this item
+ var/ignition_message = tool.ignition_effect(src, user)
+ if(!ignition_message)
+ return NONE
+ user.visible_message(ignition_message)
+ toggle_burner(TRUE)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/structure/chem_separator/crowbar_act(mob/living/user, obj/item/tool)
+ deconstruct(TRUE)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/structure/chem_separator/attack_hand(mob/living/user, list/modifiers)
+ if(!QDELETED(distilled_container))
+ if(!SStgui.get_open_ui(user, src)) //for convinience open ui first then interact with beakers if you still want to
+ ui_interact(user)
+ return TRUE
+
+ if(user.put_in_hands(distilled_container))
+ to_chat(user, span_notice("you take out the output flask."))
+ update_appearance(UPDATE_OVERLAYS)
+ return TRUE
+
+ return ..()
+
+/obj/structure/chem_separator/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers)
+ . = NONE
+ if(user.combat_mode || tool.item_flags & ABSTRACT || tool.flags_1 & HOLOGRAM_1 || !user.can_perform_action(src, ALLOW_SILICON_REACH))
+ return ITEM_INTERACT_SKIP_TO_ATTACK
+
+ if(is_reagent_container(tool) && tool.is_open_container())
+ //transfer old container
+ if(!QDELETED(fuel_container))
+ user.put_in_hands(fuel_container)
+
+ //add new container
+ if(!user.transferItemToLoc(tool, src))
+ to_chat(user, span_warning("[tool] is stuck in your hand."))
+ return ITEM_INTERACT_BLOCKING
+ fuel_container = tool
+ balloon_alert(user, "fuel container added.")
+
+ ui_interact(user)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/structure/chem_separator/attack_hand_secondary(mob/user, list/modifiers)
+ if(!QDELETED(fuel_container))
+ if(!SStgui.get_open_ui(user, src)) //for convinience open ui first then interact with beakers if you still want to
+ ui_interact(user)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ if(user.put_in_hands(fuel_container))
+ to_chat(user, span_notice("you take out the burner fuel container"))
+ toggle_burner(FALSE)
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
+ return ..()
+
+///Returns the coefficient of cooling of reagents, taking into consideration the condenser
+/obj/structure/chem_separator/proc/get_cool_coefficient()
+ PRIVATE_PROC(TRUE)
+
+ var/coefficient = 0.2
+
+ var/fuel_coefficient = get_ignition_coefficient()
+ if(condenser_installed && condenser_on && fuel_coefficient > 0)
+ var/datum/reagents/fuel = fuel_container.reagents
+ if(fuel.remove_all(0.15))
+ coefficient += fuel_coefficient
+ else
+ condenser_on = FALSE
+
+ return coefficient
/obj/structure/chem_separator/process(seconds_per_tick)
- var/datum/gas_mixture/air = return_air()
- if(!can_process(air))
- return stop()
- if(isturf(loc))
- var/turf/location = loc
- location.hotspot_expose(exposed_temperature = 700, exposed_volume = 5)
- if(reagents.chem_temp < required_temp)
- reagents.adjust_thermal_energy(heating_rate * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * reagents.maximum_volume)
- reagents.chem_temp = min(reagents.chem_temp, required_temp)
- update_appearance(UPDATE_ICON)
- return
- if(reagents.chem_temp >= required_temp)
- if(!boiling)
+ if(!reagents.total_volume)
+ if(QDELETED(distilled_container) || !distilled_container.reagents.total_volume)
+ boiling = FALSE
+ soundloop.stop()
+ toggle_burner(FALSE)
+ return PROCESS_KILL
+
+ //if burner in on attempt to heat the reagents
+ if(burner_on)
+ var/can_process = TRUE
+
+ //do we have good quality fuel to burn
+ var/fuel_coefficient = get_ignition_coefficient()
+ if(!fuel_coefficient)
+ can_process = FALSE
+ toggle_burner(FALSE)
+
+ var/knob_ratio = burner_knob / MAX_BURNER_KNOB_SETTINGS
+ var/datum/reagents/fuel = fuel_container.reagents
+
+ //consume some air after we have validated we have some good fuel. Only if we don't already use O2 as a fuel
+ if(can_process && !fuel.has_reagent(/datum/reagent/oxygen))
+ var/datum/gas_mixture/air = return_air()
+ if(!air.remove_specific(/datum/gas/oxygen, 0.01 + (0.04 * knob_ratio))) //can burn anywhere between 0.01 & 0.05 moles of air based on the knob settings
+ can_process = FALSE
+ toggle_burner(FALSE)
+
+ //burn some fuel if we combusted some air
+ if(can_process)
+ if(!fuel.remove_all(0.01 + (0.19 * knob_ratio))) //can burn anywhere between 0.01 & 0.2 units of fuel based on the knob settings
+ can_process = FALSE
+ toggle_burner(FALSE)
+
+ //finally heat the mixture
+ if(can_process)
+ reagents.adjust_thermal_energy((1000 - reagents.chem_temp) * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * (0.05 + (0.45 * knob_ratio)) * fuel_coefficient)
+ reagents.handle_reactions()
+ else if(reagents.chem_temp > DEFAULT_REAGENT_TEMPERATURE) //the container cools down if there is no flame heating it till it reaches room temps
+ reagents.adjust_thermal_energy((DEFAULT_REAGENT_TEMPERATURE - reagents.chem_temp) * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * get_cool_coefficient())
+ reagents.handle_reactions()
+
+ //the target distilation beaker also cools down
+ if(!QDELETED(distilled_container) && distilled_container.reagents.chem_temp > DEFAULT_REAGENT_TEMPERATURE)
+ var/datum/reagents/distiled_reagents = distilled_container.reagents
+ distiled_reagents.adjust_thermal_energy((DEFAULT_REAGENT_TEMPERATURE - distiled_reagents.chem_temp) * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * get_cool_coefficient())
+ distiled_reagents.handle_reactions()
+
+ //the distilation process checks the individual boiling point of each reagent based on their mass for seperation
+ boiling = FALSE
+ for(var/datum/reagent/reg in reagents.reagent_list)
+ var/bp = get_boiling_point(reg)
+ //we can now distil this
+ if(reagents.chem_temp > bp)
+ //distilation rate increases as temps go up peaking at 12 units per tick(at 200k above boiling point)
+ var/amount = 2 + ((reagents.chem_temp - bp) / 200) * 10
+ if(!QDELETED(distilled_container))
+ reagents.trans_to(distilled_container.reagents, amount, target_id = reg.type)
+ else //no target container means reagents vanish into thin air i.e leak out
+ reagents.remove_reagent(reg.type, amount)
boiling = TRUE
+
+ //boiling sound effect
+ if(boiling)
+ if(!loop_started)
soundloop.start()
- var/vapor_amount = distillation_rate * seconds_per_tick
- // Vapor to condenser
- reagents.trans_to(condenser, vapor_amount, target_id = separating_reagent.type)
- // Cool the vapor down
- condenser.set_temperature(air.temperature)
- // Condense into container
- condenser.trans_to(beaker.reagents, condenser.total_volume)
- else if (boiling)
- boiling = FALSE
+ loop_started = TRUE
+ update_appearance(UPDATE_OVERLAYS)
+ else
soundloop.stop()
- update_appearance(UPDATE_ICON)
+ loop_started = FALSE
+ update_appearance(UPDATE_OVERLAYS)
/obj/structure/chem_separator/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -247,46 +441,102 @@
ui = new(user, src, "ChemSeparator", name)
ui.open()
+/obj/structure/chem_separator/ui_static_data(mob/user)
+ return list(
+ "condenser_installed" = condenser_installed,
+ "max_burner_knob_settings" = MAX_BURNER_KNOB_SETTINGS,
+ )
+
/obj/structure/chem_separator/ui_data(mob/user)
- var/list/data = list()
- data["is_burning"] = burning
- data["temperature"] = reagents.chem_temp - T0C // Thermometer is in Celsius
- data["own_total_volume"] = reagents.total_volume
- data["own_maximum_volume"] = reagents.maximum_volume
- data["own_reagent_color"] = mix_color_from_reagents(reagents.reagent_list)
- data["beaker"] = !!beaker
- if(beaker)
- data["beaker_total_volume"] = beaker.reagents.total_volume
- data["beaker_maximum_volume"] = beaker.reagents.maximum_volume
- data["beaker_reagent_color"] = mix_color_from_reagents(beaker.reagents.reagent_list)
- return data
-
-/obj/structure/chem_separator/ui_act(action, params)
- if(..())
- return TRUE
+ . = list()
+
+ //distilation flask data
+ var/list/flask_data = list()
+ flask_data["total_volume"] = reagents.total_volume
+ flask_data["maximum_volume"] = reagents.maximum_volume
+ flask_data["temp"] = reagents.chem_temp
+ flask_data["color"] = mix_color_from_reagents(reagents.reagent_list)
+ .["flask"] = flask_data
+
+ //distilled beaker data
+ var/list/distilled_data = null
+ if(!QDELETED(distilled_container))
+ var/datum/reagents/distilled_reagents = distilled_container.reagents
+
+ distilled_data = list()
+ distilled_data["total_volume"] = distilled_reagents.total_volume
+ distilled_data["maximum_volume"] = distilled_reagents.maximum_volume
+ distilled_data["temp"] = distilled_reagents.chem_temp
+ distilled_data["color"] = mix_color_from_reagents(distilled_reagents.reagent_list)
+ .["beaker"] = distilled_data
+
+ //burner fuel data
+ var/list/fuel_data = null
+ if(!QDELETED(fuel_container))
+ var/datum/reagents/fuel_reagents = fuel_container.reagents
+
+ fuel_data = list()
+ fuel_data["total_volume"] = fuel_reagents.total_volume
+ fuel_data["maximum_volume"] = fuel_reagents.maximum_volume
+ fuel_data["temp"] = fuel_reagents.chem_temp
+ fuel_data["color"] = mix_color_from_reagents(fuel_reagents.reagent_list)
+ .["fuel"] = fuel_data
+
+ //Knob setting
+ .["burner_on"] = burner_on
+ .["knob"] = burner_knob
+
+ //Condenser setting
+ .["condenser_on"] = condenser_on
+
+/obj/structure/chem_separator/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
switch(action)
- if("load")
- load()
- if("unload")
- unload()
- if("start")
- playsound(usr, 'sound/effects/pop.ogg', 30, ignore_walls = FALSE)
- start()
- if("stop")
- stop()
- if("eject")
- replace_beaker(usr)
- return TRUE
-
-/datum/crafting_recipe/chem_separator
- name = "chemical separator"
- result = /obj/structure/chem_separator
- tool_behaviors = list(TOOL_WELDER)
- time = 5 SECONDS
- reqs = list(
- /obj/item/stack/sheet/mineral/wood = 1,
- /obj/item/stack/sheet/glass = 1,
- /obj/item/burner = 1,
- /obj/item/thermometer = 1,
- )
- category = CAT_CHEMISTRY
+ if("drain")
+ if(QDELETED(distilled_container) || !reagents.total_volume)
+ return FALSE
+
+ if(reagents.trans_to(distilled_container.reagents, reagents.maximum_volume))
+ toggle_burner(FALSE)
+ return TRUE
+
+ if("filter")
+ if(QDELETED(distilled_container) || !distilled_container.reagents.total_volume)
+ return FALSE
+
+ if(distilled_container.reagents.trans_to(reagents, reagents.maximum_volume))
+ update_appearance(UPDATE_OVERLAYS)
+ return TRUE
+
+ if("knob")
+ var/setting = params["amount"]
+ if(isnull(setting))
+ return FALSE
+
+ setting = text2num(setting)
+ if(!setting)
+ return FALSE
+
+ burner_knob = clamp(setting, 1, MAX_BURNER_KNOB_SETTINGS)
+ if(burner_on)
+ set_light(ROUND_UP(2 * (burner_knob / MAX_BURNER_KNOB_SETTINGS)))
+ return TRUE
+
+ if("cool")
+ if(!condenser_installed)
+ return FALSE
+
+ condenser_on = !condenser_on
+ return TRUE
+
+/obj/structure/chem_separator/click_alt(mob/user)
+ if(!burner_on)
+ return CLICK_ACTION_BLOCKING
+
+ toggle_burner(FALSE)
+ return CLICK_ACTION_SUCCESS
+
+#undef MAX_BURNER_KNOB_SETTINGS
diff --git a/tgui/packages/tgui/interfaces/ChemSeparator.tsx b/tgui/packages/tgui/interfaces/ChemSeparator.tsx
index 7d6d645f17a..e6be430466f 100644
--- a/tgui/packages/tgui/interfaces/ChemSeparator.tsx
+++ b/tgui/packages/tgui/interfaces/ChemSeparator.tsx
@@ -4,86 +4,196 @@ import { useBackend } from '../backend';
import {
Box,
Button,
+ Knob,
LabeledList,
- NoticeBox,
ProgressBar,
- Section,
+ Stack,
} from '../components';
import { Window } from '../layouts';
+type RegHolderData = {
+ total_volume: number;
+ maximum_volume: number;
+ temp: number;
+ color: string;
+};
+
type Data = {
- is_burning: BooleanLike;
- temperature: number;
- own_total_volume: number;
- own_maximum_volume: number;
- own_reagent_color: string;
- beaker: BooleanLike;
- beaker_total_volume: number;
- beaker_maximum_volume: number;
- beaker_reagent_color: string;
+ flask: RegHolderData;
+ beaker: RegHolderData;
+ fuel: RegHolderData;
+ burner_on: BooleanLike;
+ knob: number;
+ condenser_installed: BooleanLike;
+ condenser_on: BooleanLike;
+ max_burner_knob_settings: number;
};
export const ChemSeparator = (props) => {
const { act, data } = useBackend();
+ const {
+ flask,
+ beaker,
+ fuel,
+ burner_on,
+ knob,
+ condenser_installed,
+ condenser_on,
+ max_burner_knob_settings,
+ } = data;
+
return (
-
+
-
-
+
+
+ Flask:
+
+ }
+ >
+
+
+
+
+ {`${Math.ceil(flask.total_volume)} of ${
+ flask.maximum_volume
+ } units at ${Math.ceil(flask.temp)}K`}
+
+
+
+
+
+
+
+
+ {beaker && (
+ label={
+
+ Beaker:
+
+ }
+ >
+
+
+
+
+ {`${Math.ceil(beaker.total_volume)} of ${
+ beaker.maximum_volume
+ } units at ${Math.ceil(beaker.temp)}K`}
+
+
+
+
+
+
+ )}
+
+ Burner Knob:
+
+ }
+ >
+
+ act('knob', {
+ amount: value,
+ })
+ }
+ />
+
+ {fuel && (
+
+ Fuel Source:
}
>
{
textShadow: '1px 1px 0 black',
}}
>
- {`${Math.ceil(data.own_total_volume)} of ${
- data.own_maximum_volume
- } units at ${Math.ceil(data.temperature)}°C`}
+ {`${Math.ceil(fuel.total_volume)} of ${
+ fuel.maximum_volume
+ } units at ${Math.ceil(fuel.temp)}K`}
- {data.beaker ? (
+ )}
+ {!!condenser_installed &&
+ (flask.total_volume > 0 || beaker?.total_volume > 0) &&
+ fuel?.total_volume > 0 && (
- = data.own_maximum_volume
- }
- onClick={() => act('load')}
- />
- act('eject')}
- />
-
- }
- >
-
+ label={
- {`${Math.ceil(data.beaker_total_volume)} of ${
- data.beaker_maximum_volume
- } units`}
+ Cooling:
-
-
- ) : (
-
- No container inserted.
+ }
+ >
+ act('cool')}
+ >
+ Start
+
)}
-
-
+
);