[MIRROR] Refactored ghetto chem separator (#27958)

* Refactored ghetto chem separator (#83275)

## About The Pull Request

This completely reworks how ghetto chem separator works & brings more
depth & immersion to it. To understand the new system let's compare it
with the current one

**Old System**
1. You insert a beaker containing the reagents you want to separate.
2. You use a lighter/whatever to light a flame, Flame has _magic &
unrealistic_ properties such as
    - It will never heat the beaker above 100k
    - It doesn't burn oxygen or use fuel source to sustain it
- Distillation rate is a constant 5u/s. It doesn't increase as the
solution boils hotter

3. The apparatus sorts the reagents alphabetically? & transfers the 1st
reagent from sorted list to the target beaker. It does not care if each
individual reagent has their boiling points & how to separate them based
on that etc. It's just bland overall

**New System**
1. You insert a beaker containing the reagents you want to separate,
just lke the old system
2. You use a lighter/whatever to light a flame just like the old system,
however flame behaves in a more realistic fashion as follows
- It will heat the beaker all the way to 1000k if you let it run long
enough
- It now burns oxygen & uses a fuel source(a secondary beaker which you
must insert containing flamable reagents like oil, welding fuel etc).
When oxygen is the fuel source no O2 gas is required
    - Distillation rate starts at 2u/s & increases with rising temps
- Intensity of the flame depends on the quality of reagents used as
fuel, Oil has best flammable quality & will heat the beaker faster,
other reagents mixed in various proportions can produce better quality
flames which you can understand by examining the aparatus
    - The flame intensity can be adjusted via the burner knob setting
3. Each reagent now has their _boiling computed_ computed from their
mass allowing for realistic interactions, like having to control the
temp's just right to separate your required reagent
4. Temps automatically cooldown and you can install a condenser to
Facilitate faster cooling

Here's the demo video explaining everything

https://github.com/tgstation/tgstation/assets/110812394/ba591859-3fff-4d06-90f8-024c7266f830

## Changelog
🆑
refactor: ghetto chem separator has been reworked from scratch. See PR
83275 for details
qol: adds examines & screentips for ghetto chem separator
/🆑

* Refactored ghetto chem separator

---------

Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
This commit is contained in:
SkyratBot
2024-06-02 03:38:18 +02:00
committed by GitHub
co-authored by SyncIt21
parent 4c0b30f174
commit dc77475d0d
5 changed files with 726 additions and 370 deletions
@@ -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
+16
View File
@@ -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
@@ -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
@@ -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 <b>[reagents.total_volume]/[reagents.maximum_volume]u</b>.")
if(!QDELETED(distilled_container))
. += span_notice("The distilation beaker reads <b>[distilled_container.reagents.total_volume]/[distilled_container.reagents.maximum_volume]u</b>.")
. += 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 <b>[fuel_container.reagents.total_volume]/[fuel_container.reagents.maximum_volume]u</b>.")
. += 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
/**
* 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)
/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 ..()
//use the constant mass set on init
reg = GLOB.chemical_reagents_list[reg.type]
/// 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
//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)
/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
/// 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)
///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)
/// 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 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
/// 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)
START_PROCESSING(SSobj, src)
balloon_alert(user, "distillation container added.")
/// 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
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_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
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,
/obj/structure/chem_separator/ui_static_data(mob/user)
return list(
"condenser_installed" = condenser_installed,
"max_burner_knob_settings" = MAX_BURNER_KNOB_SETTINGS,
)
category = CAT_CHEMISTRY
/obj/structure/chem_separator/ui_data(mob/user)
. = 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("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
+205 -123
View File
@@ -4,136 +4,69 @@ 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<Data>();
const {
flask,
beaker,
fuel,
burner_on,
knob,
condenser_installed,
condenser_on,
max_burner_knob_settings,
} = data;
return (
<Window width={470} height={130}>
<Window width={370} height={215}>
<Window.Content>
<Section fill>
<LabeledList>
<LabeledList.Item
label="Separator"
buttons={
<Box my={1}>
<Button
mr={2}
width={6}
lineHeight={2}
align="center"
content="Drain"
icon="arrow-down"
disabled={
data.is_burning ||
!data.own_total_volume ||
!data.beaker ||
data.beaker_total_volume >= data.beaker_maximum_volume
}
onClick={() => act('unload')}
/>
{!data.is_burning ? (
<Button
width={6}
lineHeight={2}
align="center"
content="Start"
icon="filter"
color="good"
disabled={
!data.own_total_volume ||
!data.beaker ||
data.beaker_total_volume >= data.beaker_maximum_volume
}
onClick={() => act('start')}
/>
) : (
<Button
width={6}
lineHeight={2}
align="center"
content="Stop"
icon="ban"
color="bad"
onClick={() => act('stop')}
/>
)}
</Box>
}
>
<ProgressBar
height={2}
value={data.own_total_volume}
minValue={0}
maxValue={data.own_maximum_volume}
color={data.own_reagent_color}
>
<Box
lineHeight={1.9}
style={{
textShadow: '1px 1px 0 black',
}}
>
{`${Math.ceil(data.own_total_volume)} of ${
data.own_maximum_volume
} units at ${Math.ceil(data.temperature)}°C`}
</Box>
</ProgressBar>
</LabeledList.Item>
{data.beaker ? (
<LabeledList.Item
label="Container"
buttons={
<Box my={1}>
<Button
mr={2}
width={6}
lineHeight={2}
align="center"
content="Fill"
icon="arrow-up"
disabled={
data.is_burning ||
!data.beaker_total_volume ||
data.own_total_volume >= data.own_maximum_volume
}
onClick={() => act('load')}
/>
<Button
width={6}
lineHeight={2}
align="center"
icon="eject"
content="Eject"
disabled={data.is_burning}
onClick={() => act('eject')}
/>
</Box>
}
<LabeledList>
<LabeledList.Item
label={
<Box
style={{
transform: 'translate(20%, -50%)',
width: '57px',
}}
>
Flask:
</Box>
}
>
<Stack fill>
<Stack.Item ml="27px">
<ProgressBar
height={2}
value={data.beaker_total_volume}
minValue={0}
maxValue={data.beaker_maximum_volume}
color={data.beaker_reagent_color}
maxValue={flask.maximum_volume}
value={flask.total_volume}
color={flask.color}
width="170px"
>
<Box
lineHeight={1.9}
@@ -141,19 +74,168 @@ export const ChemSeparator = (props) => {
textShadow: '1px 1px 0 black',
}}
>
{`${Math.ceil(data.beaker_total_volume)} of ${
data.beaker_maximum_volume
} units`}
{`${Math.ceil(flask.total_volume)} of ${
flask.maximum_volume
} units at ${Math.ceil(flask.temp)}K`}
</Box>
</ProgressBar>
</LabeledList.Item>
) : (
<LabeledList.Item label="Container">
<NoticeBox my={0.7}>No container inserted.</NoticeBox>
</Stack.Item>
<Stack.Item>
<Button
mr={2}
width={6}
lineHeight={2}
align="center"
icon="arrow-down"
disabled={flask.total_volume <= 0 || !beaker}
onClick={() => act('drain')}
>
Drain
</Button>
</Stack.Item>
</Stack>
</LabeledList.Item>
{beaker && (
<LabeledList.Item
label={
<Box
style={{
transform: 'translate(20%, -50%)',
width: '57px',
}}
>
Beaker:
</Box>
}
>
<Stack fill>
<Stack.Item ml="27px">
<ProgressBar
height={2}
minValue={0}
maxValue={beaker.maximum_volume}
value={beaker.total_volume}
color={beaker.color}
width="170px"
>
<Box
lineHeight={1.9}
style={{
textShadow: '1px 1px 0 black',
}}
>
{`${Math.ceil(beaker.total_volume)} of ${
beaker.maximum_volume
} units at ${Math.ceil(beaker.temp)}K`}
</Box>
</ProgressBar>
</Stack.Item>
<Stack.Item>
<Button
mr={2}
width={6}
lineHeight={2}
align="center"
icon="filter"
disabled={beaker.total_volume <= 0}
onClick={() => act('filter')}
>
Filter
</Button>
</Stack.Item>
</Stack>
</LabeledList.Item>
)}
<LabeledList.Item
label={
<Box
style={{
transform: 'translate(20%, -50%)',
width: '57px',
}}
>
Burner Knob:
</Box>
}
>
<Knob
ml="30px"
size={1.3}
minValue={1}
maxValue={max_burner_knob_settings}
step={1}
stepPixelSize={10}
value={knob}
onDrag={(_e, value) =>
act('knob', {
amount: value,
})
}
/>
</LabeledList.Item>
{fuel && (
<LabeledList.Item
label={
<Box
style={{
transform: 'translate(20%, -20%)',
width: '57px',
}}
>
Fuel Source:
</Box>
}
>
<ProgressBar
height={2}
minValue={0}
maxValue={fuel.maximum_volume}
value={fuel.total_volume}
color={fuel.color}
maxWidth="170px"
ml="25px"
>
<Box
lineHeight={1.9}
style={{
textShadow: '1px 1px 0 black',
}}
>
{`${Math.ceil(fuel.total_volume)} of ${
fuel.maximum_volume
} units at ${Math.ceil(fuel.temp)}K`}
</Box>
</ProgressBar>
</LabeledList.Item>
)}
{!!condenser_installed &&
(flask.total_volume > 0 || beaker?.total_volume > 0) &&
fuel?.total_volume > 0 && (
<LabeledList.Item
label={
<Box
style={{
transform: 'translate(20%, -20%)',
width: '57px',
}}
>
Cooling:
</Box>
}
>
<Button
icon="thermometer-full"
disabled={burner_on}
selected={condenser_on}
fontSize="15px"
ml="25px"
onClick={() => act('cool')}
>
Start
</Button>
</LabeledList.Item>
)}
</LabeledList>
</Section>
</LabeledList>
</Window.Content>
</Window>
);