diff --git a/code/__DEFINES/achievements.dm b/code/__DEFINES/achievements.dm
index 7af89f2673a..344d1d0e80e 100644
--- a/code/__DEFINES/achievements.dm
+++ b/code/__DEFINES/achievements.dm
@@ -43,7 +43,6 @@
#define MEDAL_COSMOS_ASCENSION "Cosmos"
#define MEDAL_LOCK_ASCENSION "Knock"
#define MEDAL_TOOLBOX_SOUL "Toolsoul"
-#define MEDAL_CHEM_TUT "Beginner Chemist"
#define MEDAL_HOT_DAMN "Hot Damn!"
#define MEDAL_CAYENNE_DISK "Very Important Piscis"
#define MEDAL_TRAM_SURFER "Tram Surfer"
diff --git a/code/datums/achievements/job_achievements.dm b/code/datums/achievements/job_achievements.dm
index 10835b895b8..6aafbee8e68 100644
--- a/code/datums/achievements/job_achievements.dm
+++ b/code/datums/achievements/job_achievements.dm
@@ -24,12 +24,6 @@
database_id = MEDAL_HELBITALJANKEN
icon = "helbital"
-/datum/award/achievement/jobs/chemistry_tut
- name = "Perfect chemistry blossom"
- desc = "Passed the chemistry tutorial with perfect purity!"
- database_id = MEDAL_CHEM_TUT
- icon = "chem_tut"
-
//mining
/datum/award/achievement/jobs/frenching
diff --git a/code/modules/asset_cache/assets/tutorial_advisors.dm b/code/modules/asset_cache/assets/tutorial_advisors.dm
deleted file mode 100644
index da80e8904d3..00000000000
--- a/code/modules/asset_cache/assets/tutorial_advisors.dm
+++ /dev/null
@@ -1,4 +0,0 @@
-/datum/asset/simple/tutorial_advisors
- assets = list(
- "chem_help_advisor.gif" = 'icons/ui_icons/advisors/chem_help_advisor.gif',
- )
diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm
index a95f805130a..f0113153c92 100644
--- a/code/modules/reagents/chemistry/machinery/chem_heater.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm
@@ -1,13 +1,3 @@
-///Tutorial states
-#define TUT_NO_BUFFER 50
-#define TUT_START 1
-#define TUT_HAS_REAGENTS 2
-#define TUT_IS_ACTIVE 3
-#define TUT_IS_REACTING 4
-#define TUT_FAIL 4.5
-#define TUT_COMPLETE 5
-#define TUT_MISSING 10
-
/obj/machinery/chem_heater
name = "reaction chamber" //Maybe this name is more accurate?
density = TRUE
@@ -19,29 +9,24 @@
resistance_flags = FIRE_PROOF | ACID_PROOF
circuit = /obj/item/circuitboard/machine/chem_heater
+ /// The beaker inside this machine
var/obj/item/reagent_containers/beaker = null
+ /// The temperature this heater is trying to acheive
var/target_temperature = 300
+ /// The energy used by the heater to achieve the target temperature
var/heater_coefficient = 0.05
+ /// Is the heater on or off
var/on = FALSE
+ /// How much buffer are we transferig per click
var/dispense_volume = 1
- //The list of active clients using this heater, so that we can update the UI on a reaction_step. I assume there are multiple clients possible.
- var/list/ui_client_list
- ///If the user has the tutorial enabled
- var/tutorial_active = FALSE
- ///What state we're at in the tutorial
- var/tutorial_state = 0
/obj/machinery/chem_heater/Initialize(mapload)
. = ..()
- create_reagents(200, NO_REACT)//Lets save some calculations here
- //TODO: comsig reaction_start and reaction_end to enable/disable the UI autoupdater - this doesn't work presently as there's a hard divide between instant and processed reactions
+ create_reagents(200, NO_REACT)
+ register_context()
-/obj/machinery/chem_heater/deconstruct(disassembled)
- . = ..()
- if(beaker && disassembled)
- UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP)
- beaker.forceMove(drop_location())
- beaker = null
+/obj/machinery/chem_heater/on_deconstruction()
+ beaker?.forceMove(drop_location())
/obj/machinery/chem_heater/Destroy()
if(beaker)
@@ -52,6 +37,7 @@
/obj/machinery/chem_heater/Exited(atom/movable/gone, direction)
. = ..()
if(gone == beaker)
+ UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP)
beaker = null
update_appearance()
@@ -63,7 +49,7 @@
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return
- if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH|FORBID_TELEKINESIS_REACH))
+ if(!user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
return
replace_beaker(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
@@ -74,17 +60,27 @@
/obj/machinery/chem_heater/attack_ai_secondary(mob/user, list/modifiers)
return attack_hand_secondary(user, modifiers)
+/**
+ * Replace or eject the beaker inside this machine
+ * Arguments
+ * * mob/living/user - the player operating this machine
+ * * obj/item/reagent_containers/new_beaker - the new beaker to replace the current one if not null else it will just eject
+ */
/obj/machinery/chem_heater/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker)
- if(!user)
- return FALSE
- if(beaker)
- UnregisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP)
+ PRIVATE_PROC(TRUE)
+
+ if(!QDELETED(beaker))
try_put_in_hand(beaker, user)
- beaker = null
- if(new_beaker)
+
+ if(!QDELETED(new_beaker))
+ if(!user.transferItemToLoc(new_beaker, src))
+ update_appearance()
+ return FALSE
beaker = new_beaker
- RegisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP, PROC_REF(on_reaction_step))
+ RegisterSignal(beaker.reagents, COMSIG_REAGENTS_REACTION_STEP, TYPE_PROC_REF(/obj/machinery/chem_heater, refresh_ui))
+
update_appearance()
+
return TRUE
/obj/machinery/chem_heater/RefreshParts()
@@ -93,206 +89,175 @@
for(var/datum/stock_part/micro_laser/micro_laser in component_parts)
heater_coefficient *= micro_laser.tier
+/obj/machinery/chem_heater/proc/refresh_ui()
+ SIGNAL_HANDLER
+
+ SStgui.update_uis(src)
+
+/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))
- . += span_notice("The status display reads: Heating reagents at [heater_coefficient*1000]% speed.")
+ . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.")
+ if(!QDELETED(beaker))
+ . += span_notice("It has a beaker of [beaker.reagents.total_volume] units capacity.")
+ if(beaker.reagents.is_reacting)
+ . += span_notice("Its contents are currently reacting.")
+ else
+ . += span_warning("There is no beaker inserted.")
+ . += span_notice("Its heating is turned [on ? "On" : "Off"].")
+ . += span_notice("The status display reads: Heating reagents at [heater_coefficient * 1000]% speed.")
+ if(panel_open)
+ . += span_notice("Its panel is open and can now be [EXAMINE_HINT("pried")] apart.")
+ else
+ . += span_notice("Its panel can be [EXAMINE_HINT("pried")] open")
/obj/machinery/chem_heater/process(seconds_per_tick)
- ..()
- //Tutorial logics
- if(tutorial_active)
- switch(tutorial_state)
- if(TUT_NO_BUFFER)
- if(reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer, 5) && reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer, 5))
- tutorial_state = TUT_START
-
- if(TUT_START)
- if(!reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer, 5) || !reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer, 5))
- tutorial_state = TUT_NO_BUFFER
- return
- if(beaker?.reagents.has_reagent(/datum/reagent/mercury, 10) || beaker?.reagents.has_reagent(/datum/reagent/chlorine, 10))
- tutorial_state = TUT_HAS_REAGENTS
- if(TUT_HAS_REAGENTS)
- if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury, 9)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine, 9)))
- tutorial_state = TUT_MISSING
- return
- if(beaker?.reagents.chem_temp > 374)//If they heated it up as asked
- tutorial_state = TUT_IS_ACTIVE
- target_temperature = 375
- beaker.reagents.chem_temp = 375
-
- if(TUT_IS_ACTIVE)
- if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine))) //Slightly concerned that people might take ages to read and it'll react anyways
- tutorial_state = TUT_MISSING
- return
- if(length(beaker?.reagents.reaction_list) == 1)//Only fudge numbers for our intentful reaction
- beaker.reagents.chem_temp = 375
-
- if(target_temperature >= 390)
- tutorial_state = TUT_IS_REACTING
-
- if(TUT_IS_REACTING)
- if(!(beaker?.reagents.has_reagent(/datum/reagent/mercury)) || !(beaker?.reagents.has_reagent(/datum/reagent/chlorine)))
- tutorial_state = TUT_COMPLETE
-
- if(TUT_COMPLETE)
- if(beaker?.reagents.has_reagent(/datum/reagent/consumable/failed_reaction))
- tutorial_state = TUT_FAIL
- return
- if(!beaker?.reagents.has_reagent(/datum/reagent/medicine/calomel))
- tutorial_state = TUT_MISSING
-
- if(machine_stat & NOPOWER)
+ if(!on || (machine_stat & NOPOWER) || QDELETED(beaker))
return
- if(on)
- if(beaker?.reagents.total_volume)
- if(beaker.reagents.is_reacting)//on_reaction_step() handles this
- return
- //keep constant with the chemical acclimator please
- beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume)
+
+ if(beaker.reagents.total_volume)
+ var/randomness = 1
+ if(beaker.reagents.is_reacting) //Give it a little wiggle room since we're actively reacting
+ randomness = rand(8, 11) * 0.1
+
+ //keep constant with the chemical acclimator please
+ beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume * randomness)
+ if(!beaker.reagents.is_reacting)
beaker.reagents.handle_reactions()
- use_power(active_power_usage * seconds_per_tick)
+ //use power
+ use_power(active_power_usage * seconds_per_tick)
/obj/machinery/chem_heater/wrench_act(mob/living/user, obj/item/tool)
- . = ..()
- default_unfasten_wrench(user, tool)
- return ITEM_INTERACT_SUCCESS
+ . = ITEM_INTERACT_BLOCKING
+ if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN)
+ return ITEM_INTERACT_SUCCESS
-/obj/machinery/chem_heater/attackby(obj/item/I, mob/user, params)
- if(default_deconstruction_screwdriver(user, "mixer0b", "mixer0b", I))
- return
+/obj/machinery/chem_heater/screwdriver_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(default_deconstruction_screwdriver(user, "mixer0b", "[base_icon_state][beaker ? 1 : 0]b", tool))
+ return ITEM_INTERACT_SUCCESS
- if(default_deconstruction_crowbar(I))
- return
+/obj/machinery/chem_heater/crowbar_act(mob/living/user, obj/item/tool)
+ . = ITEM_INTERACT_BLOCKING
+ if(default_deconstruction_crowbar(tool))
+ return ITEM_INTERACT_SUCCESS
- if(is_reagent_container(I) && !(I.item_flags & ABSTRACT) && I.is_open_container())
- . = TRUE //no afterattack
- var/obj/item/reagent_containers/B = I
- if(!user.transferItemToLoc(B, src))
- return
- replace_beaker(user, B)
- to_chat(user, span_notice("You add [B] to [src]."))
- ui_interact(user)
- update_appearance()
- return
+/obj/machinery/chem_heater/attackby(obj/item/held_item, mob/user, params)
+ if((held_item.item_flags & ABSTRACT) || (held_item.flags_1 & HOLOGRAM_1))
+ return ..()
if(beaker)
- if(istype(I, /obj/item/reagent_containers/dropper))
- var/obj/item/reagent_containers/dropper/D = I
- D.afterattack(beaker, user, 1)
- return
- if(istype(I, /obj/item/reagent_containers/syringe))
- var/obj/item/reagent_containers/syringe/S = I
- S.afterattack(beaker, user, 1)
- return
- return ..()
+ if(istype(held_item, /obj/item/reagent_containers/dropper) || istype(held_item, /obj/item/reagent_containers/syringe))
+ var/obj/item/reagent_containers/injector = held_item
+ injector.afterattack(beaker, user, proximity_flag = TRUE)
+ return TRUE
+ if(is_reagent_container(held_item) && held_item.is_open_container())
+ replace_beaker(user, held_item)
+ balloon_alert(user, "beaker added!")
+ return TRUE
-/obj/machinery/chem_heater/on_deconstruction()
- replace_beaker()
return ..()
-///Forces a UI update every time a reaction step happens inside of the beaker it contains. This is so the UI is in sync with the reaction since it's important that the output matches the current conditions for pH adjustment and temperature.
-/obj/machinery/chem_heater/proc/on_reaction_step(datum/reagents/holder, num_reactions, seconds_per_tick)
- SIGNAL_HANDLER
- if(on)
- holder.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume * (rand(8,11) * 0.1))//Give it a little wiggle room since we're actively reacting
- for(var/ui_client in ui_client_list)
- var/datum/tgui/ui = ui_client
- if(!ui)
- stack_trace("Warning: UI in UI client list is missing in [src] (chem_heater)")
- remove_ui_client_list(ui)
- continue
- ui.send_update()
-
/obj/machinery/chem_heater/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ChemHeater", name)
ui.open()
- add_ui_client_list(ui)
-
-/obj/machinery/chem_heater/ui_close(mob/user)
- for(var/ui_client in ui_client_list)
- var/datum/tgui/ui = ui_client
- if(ui.user == user)
- remove_ui_client_list(ui)
- return ..()
-
-/*
-*This adds an open ui client to the list - so that it can be force updated from reaction mechanisms.
-* After adding it to the list, it enables a signal incase the ui is deleted - which will call a method to remove it from the list
-* This is mostly to ensure we don't have defunct ui instances stored from any condition.
-*/
-/obj/machinery/chem_heater/proc/add_ui_client_list(new_ui)
- LAZYADD(ui_client_list, new_ui)
- RegisterSignal(new_ui, COMSIG_QDELETING, PROC_REF(on_ui_deletion))
-
-///This removes an open ui instance from the ui list and deregsiters the signal
-/obj/machinery/chem_heater/proc/remove_ui_client_list(old_ui)
- UnregisterSignal(old_ui, COMSIG_QDELETING)
- LAZYREMOVE(ui_client_list, old_ui)
-
-///This catches a signal and uses it to delete the ui instance from the list
-/obj/machinery/chem_heater/proc/on_ui_deletion(datum/tgui/source, force)
- SIGNAL_HANDLER
- remove_ui_client_list(source)
-
-/obj/machinery/chem_heater/ui_assets()
- . = ..() || list()
- . += get_asset_datum(/datum/asset/simple/tutorial_advisors)
/obj/machinery/chem_heater/ui_data(mob/user)
- var/data = list()
- data["targetTemp"] = target_temperature
- data["isActive"] = on
- data["isBeakerLoaded"] = beaker ? 1 : 0
+ . = list()
+ .["targetTemp"] = target_temperature
+ .["isActive"] = on
+ .["upgradeLevel"] = heater_coefficient * 10
- data["currentTemp"] = beaker ? beaker.reagents.chem_temp : null
- data["beakerCurrentVolume"] = beaker ? round(beaker.reagents.total_volume, 0.01) : null
- data["beakerMaxVolume"] = beaker ? beaker.volume : null
- data["currentpH"] = beaker ? round(beaker.reagents.ph, 0.01) : null
- var/upgrade_level = heater_coefficient*10
- data["upgradeLevel"] = upgrade_level
-
- var/list/beaker_contents = list()
- for(var/r in beaker?.reagents.reagent_list)
- var/datum/reagent/reagent = r
- beaker_contents.len++
- beaker_contents[length(beaker_contents)] = list("name" = reagent.name, "volume" = round(reagent.volume, 0.01))
- data["beakerContents"] = beaker_contents
+ var/list/beaker_data = null
+ var/chem_temp = 0
+ 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)
+ 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
+ chem_temp = beaker.reagents.chem_temp
+ .["beaker"] = beaker_data
+ .["currentTemp"] = chem_temp
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
- if(!length(beaker.reagents.reaction_list))//I'm not sure why when it explodes it causes the gui to fail (it's missing danger (?) )
- stack_trace("how is this happening??")
- continue
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
if(!reagent)
continue
+ var/datum/reagents/beaker_reagents = beaker.reagents
+
+ //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 > beaker?.reagents.ph || equilibrium.reaction.optimal_ph_max < beaker?.reagents.ph)
+ if(flashing != ENABLE_FLASHING)//So that the pH meter flashes for ANY reactions out of optimal
+ if(equilibrium.reaction.optimal_ph_min > beaker_reagents.ph || equilibrium.reaction.optimal_ph_max < beaker_reagents.ph)
flashing = ENABLE_FLASHING
if(equilibrium.reaction.is_cold_recipe)
- if(equilibrium.reaction.overheat_temp > beaker?.reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT)
+ if(equilibrium.reaction.overheat_temp > beaker_reagents.chem_temp && equilibrium.reaction.overheat_temp != NO_OVERHEAT)
danger = TRUE
overheat = TRUE
else
- if(equilibrium.reaction.overheat_temp < beaker?.reagents.chem_temp)
+ if(equilibrium.reaction.overheat_temp < beaker_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
@@ -300,190 +265,98 @@
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
+ .["activeReactions"] = active_reactions
- data["acidicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/acidic_buffer)
- data["basicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/basic_buffer)
- data["dispenseVolume"] = dispense_volume
+ .["isFlashing"] = flashing
+ .["acidicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/acidic_buffer)
+ .["basicBufferVol"] = reagents.get_reagent_amount(/datum/reagent/reaction_agent/basic_buffer)
+ .["dispenseVolume"] = dispense_volume
- data["tutorialMessage"] = null
- //Tutorial output
- if(tutorial_active)
- switch(tutorial_state)
- if(TUT_NO_BUFFER)//missing buffer
- data["tutorialMessage"] = {"It looks like you’re a little low on buffers, here’s how to make more:
-
-Acidic buffer: 2 parts Sodium
- 2 parts Hydrogen
- 2 parts Ethanol
- 2 parts Water
-
-Basic buffer: 3 parts Ammonia
- 2 parts Chlorine
- 2 parts Hydrogen
- 2 parts Oxygen
-
-Heat either up to speed up the reaction.
-
-When the reactions are done, refill your chamber by pressing the Draw all buttons, to the right of the respective volume indicators.
-
-To continue with the tutorial, fill both of your acidic and alkaline volumes to at least 5u."}
- if(TUT_START)//Default start
- data["tutorialMessage"] = {"Hello and welcome to the exciting world of chemistry! This help option will teach you the basic of reactions by guiding you through a calomel reaction.
-
-For the majority of reactions, the overheat temperature is 900K, and the pH range is 5-9, though it's always worth looking up the ranges as these are changing. Calomel is no different.
-
-To continue the tutorial, insert a beaker with at least 10u mercury and 10u chlorine added."}
- if(TUT_HAS_REAGENTS) //10u Hg and Cl
- data["tutorialMessage"] = {"Good job! You'll see that at present this isn't reacting. That's because this reaction needs a minimum temperature of 375K.
-
-For the most part the hotter your reaction is, the faster it will react when it’s past it’s minimum temperature. But be careful to not heat it too much! "If your reaction is slow, your temperature is too low"!
-
-When you’re ready, set your temperature to 375K and heat up the beaker to that amount."}
- if(TUT_IS_ACTIVE) //heat 375K
- data["tutorialMessage"] = {"Great! You should see your reaction slowly progressing.
-
-Notice the pH dial on the right; the sum pH should be slowly drifting towards the left on the dial. How pure your solution is at the end depends on how well you keep your reaction within the optimal pH range. The dial will flash if any of the present reactions are outside their optimal. "If you're getting sludge, give your pH a nudge"!
-
-In a moment, we’ll increase the temperature so that our rate is faster. It’s up to you to keep your pH within the limits, so keep an eye on that dial, and get ready to add basic buffer using the injection button to the left of the volume indicator.
-
-To continue set your target temperature to 390K."}
- if(TUT_IS_REACTING) //Heat 390K
- data["tutorialMessage"] = "Stay focused on the reaction! You can do it!"
- if(TUT_FAIL) //Sludge
- data["tutorialMessage"] = "Ah, unfortunately your purity was too low and the reaction fell apart into errant sludge. Don't worry, you can always try again! Be careful though, for some reactions, failing isn't nearly as forgiving."
- if(TUT_COMPLETE) //Complete
- var/datum/reagent/calo = beaker?.reagents.has_reagent(/datum/reagent/medicine/calomel)
- if(!calo)
- tutorial_state = TUT_COMPLETE
- return
- switch(calo.purity)
- if(-INFINITY to 0.25)
- data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. That's pretty close to the fail purity of 0.15 - which can often make some reactions explode. This chem will invert into Toxic sludge when ingested by another person, and will not cause of calomel's normal effects. Sneaky, huh?"
- if(0.25 to 0.6)
- data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Normally, this reaction will resolve above 0.7 without intervention. Are you praticing impure reactions? The lower you go, the higher change you have of getting dangerous effects during a reaction. In some more dangerous reactions, you're riding a fine line between death and an inverse chem, don't forget you can always chill your reaction to give yourself more time to manage it!"
- if(0.6 to 0.75)
- data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Normally, this reaction will resolve above 0.7 without intervention. Did you maybe add too much basic buffer and go past 9? If you like - you're welcome to try again. Just double press the help button!"
- if(0.75 to 0.85)
- data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. You got pretty close to optimal! Feel free to try again if you like by double pressing the help button."
- if(0.75 to 0.99)
- data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. You got pretty close to optimal! Feel free to try again if you like by double pressing the help button, but this is a respectable purity."
- if(0.99 to 1)
- data["tutorialMessage"] = "You did it! Congratulations! I can tell you that your final purity was [calo.purity]. Your calomel is as pure as they come! You've mastered the basics of chemistry, but there's plenty more challenges on the horizon. Good luck!"
- user.client?.give_award(/datum/award/achievement/jobs/chemistry_tut, user)
- data["tutorialMessage"] += "\n\nDid you notice that your temperature increased past 390K while reacting too? That's because this reaction is exothermic (heat producing), so for some reactions you might have to adjust your target to compensate. Oh, and you can check your purity by researching and printing off a chemical analyzer at the medlathe (for now)!"
- if(TUT_MISSING) //Missing
- data["tutorialMessage"] = "Uh oh, something went wrong. Did you take the beaker out, heat it up too fast, or have other things in the beaker? Try restarting the tutorial by double pressing the help button."
-
- return data
-
-/obj/machinery/chem_heater/ui_act(action, params)
+/obj/machinery/chem_heater/ui_act(action, params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
+
switch(action)
if("power")
on = !on
- . = TRUE
+ return TRUE
+
if("temperature")
var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target)
- . = TRUE
- if(.)
- target_temperature = clamp(target, 0, 1000)
+ if(!target)
+ return FALSE
+
+ target = text2num(target)
+ if(isnull(target))
+ return FALSE
+
+ target_temperature = clamp(target, 0, 1000)
+ return TRUE
+
if("eject")
//Eject doesn't turn it off, so you can preheat for beaker swapping
- replace_beaker(usr)
- . = TRUE
+ return replace_beaker(ui.user)
+
if("acidBuffer")
var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target)
- . = TRUE
- if(.)
- move_buffer("acid", target)
+ if(!target)
+ return FALSE
+
+ target = text2num(target)
+ if(isnull(target))
+ return FALSE
+
+ return move_buffer(/datum/reagent/reaction_agent/acidic_buffer, target)
if("basicBuffer")
var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target) //Because the input is flipped
- . = TRUE
- if(.)
- move_buffer("basic", target)
+ if(!target)
+ return FALSE
+
+ target = text2num(target)
+ if(isnull(target))
+ return FALSE
+
+ return move_buffer(/datum/reagent/reaction_agent/basic_buffer, target)
if("disp_vol")
var/target = params["target"]
- if(text2num(target) != null)
- target = text2num(target) //Because the input is flipped
- . = TRUE
- if(.)
- dispense_volume = target
- if("help")
- tutorial_active = !tutorial_active
- if(tutorial_active)
- tutorial_state = 1
- return
- tutorial_state = 0
- //Refresh window size
- ui_close(usr)
- ui_interact(usr, null)
+ if(!target)
+ return FALSE
+ target = text2num(target)
+ if(isnull(target))
+ return FALSE
-///Moves a type of buffer from the heater to the beaker, or vice versa
-/obj/machinery/chem_heater/proc/move_buffer(buffer_type, volume)
- if(!beaker)
+ dispense_volume = target
+ return FALSE
+
+/**
+ * Injects either acid/base buffer into the beaker
+ * Arguments
+ * * datum/reagent/buffer_type - the type of buffer[acid, base] to inject/withdraw
+ * * volume - how much to volume to inject -ve values means withdraw
+ */
+/obj/machinery/chem_heater/proc/move_buffer(datum/reagent/buffer_type, volume)
+ PRIVATE_PROC(TRUE)
+
+ //no beaker
+ if(QDELETED(beaker))
say("No beaker found!")
- return
- if(buffer_type == "acid")
- if(volume < 0)
- var/datum/reagent/acid_reagent = beaker.reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer)
- if(!acid_reagent)
- say("Unable to find acidic buffer in beaker to draw from! Please insert a beaker containing acidic buffer.")
- return
- var/datum/reagent/acid_reagent_heater = reagents.has_reagent(/datum/reagent/reaction_agent/acidic_buffer)
- var/cur_vol = 0
- if(acid_reagent_heater)
- cur_vol = acid_reagent_heater.volume
- volume = 100 - cur_vol
- beaker.reagents.trans_to(src, volume, target_id = acid_reagent.type)//negative because we're going backwards
- return
- //We must be positive here
- reagents.trans_to(beaker, dispense_volume, target_id = /datum/reagent/reaction_agent/acidic_buffer)
- return
+ return FALSE
- if(buffer_type == "basic")
- if(volume < 0)
- var/datum/reagent/basic_reagent = beaker.reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer)
- if(!basic_reagent)
- say("Unable to find basic buffer in beaker to draw from! Please insert a beaker containing basic buffer.")
- return
- var/datum/reagent/basic_reagent_heater = reagents.has_reagent(/datum/reagent/reaction_agent/basic_buffer)
- var/cur_vol = 0
- if(basic_reagent_heater)
- cur_vol = basic_reagent_heater.volume
- volume = 100 - cur_vol
- beaker.reagents.trans_to(src, volume, target_id = basic_reagent.type)//negative because we're going backwards
- return
- reagents.trans_to(beaker, dispense_volume, target_id = /datum/reagent/reaction_agent/basic_buffer)
- return
+ //trying to absorb buffer from currently inserted beaker
+ if(volume < 0)
+ var/datum/reagent/buffer_reagent = reagents.has_reagent(buffer_type)
+ if(!buffer_reagent)
+ var/name = initial(buffer_type.name)
+ say("Unable to find [name] in beaker to draw from! Please insert a beaker containing [name].")
+ return FALSE
+ beaker.reagents.trans_to(src, (reagents.maximum_volume / 2) - buffer_reagent.volume, target_id = buffer_type)
+ return TRUE
-
-/obj/machinery/chem_heater/proc/get_purity_color(datum/equilibrium/equilibrium)
- var/_reagent = equilibrium.reaction.results[1]
- var/datum/reagent/reagent = equilibrium.holder.has_reagent(_reagent)
- // Can't be a switch due to http://www.byond.com/forum/post/2750423
- if(reagent.purity in 1 to INFINITY)
- return "blue"
- else if(reagent.purity in 0.8 to 1)
- return "green"
- else if(reagent.purity in reagent.inverse_chem_val to 0.8)
- return "olive"
- else if(reagent.purity in equilibrium.reaction.purity_min to reagent.inverse_chem_val)
- return "orange"
- else if(reagent.purity in -INFINITY to equilibrium.reaction.purity_min)
- return "red"
+ //trying to inject buffer into currently inserted beaker
+ reagents.trans_to(beaker, dispense_volume, target_id = buffer_type)
+ return TRUE
//Has a lot of buffer and is upgraded
/obj/machinery/chem_heater/debug
@@ -505,12 +378,3 @@ To continue set your target temperature to 390K."}
. = ..()
reagents.add_reagent(/datum/reagent/reaction_agent/basic_buffer, 20)
reagents.add_reagent(/datum/reagent/reaction_agent/acidic_buffer, 20)
-
-#undef TUT_NO_BUFFER
-#undef TUT_START
-#undef TUT_HAS_REAGENTS
-#undef TUT_IS_ACTIVE
-#undef TUT_IS_REACTING
-#undef TUT_FAIL
-#undef TUT_COMPLETE
-#undef TUT_MISSING
diff --git a/icons/ui_icons/achievements/achievements.dmi b/icons/ui_icons/achievements/achievements.dmi
index 5f6626c7993..337793431e8 100644
Binary files a/icons/ui_icons/achievements/achievements.dmi and b/icons/ui_icons/achievements/achievements.dmi differ
diff --git a/icons/ui_icons/advisors/chem_help_advisor.gif b/icons/ui_icons/advisors/chem_help_advisor.gif
deleted file mode 100644
index 47709c8ce4c..00000000000
Binary files a/icons/ui_icons/advisors/chem_help_advisor.gif and /dev/null differ
diff --git a/tgstation.dme b/tgstation.dme
index ab62fe3aa4a..ab9284a84d8 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3339,7 +3339,6 @@
#include "code\modules\asset_cache\assets\supplypods.dm"
#include "code\modules\asset_cache\assets\tgfont.dm"
#include "code\modules\asset_cache\assets\tgui.dm"
-#include "code\modules\asset_cache\assets\tutorial_advisors.dm"
#include "code\modules\asset_cache\assets\uplink.dm"
#include "code\modules\asset_cache\assets\vending.dm"
#include "code\modules\asset_cache\assets\vv.dm"
diff --git a/tgui/packages/tgui/interfaces/ChemHeater.jsx b/tgui/packages/tgui/interfaces/ChemHeater.tsx
similarity index 79%
rename from tgui/packages/tgui/interfaces/ChemHeater.jsx
rename to tgui/packages/tgui/interfaces/ChemHeater.tsx
index 88634b5736e..c522686b411 100644
--- a/tgui/packages/tgui/interfaces/ChemHeater.jsx
+++ b/tgui/packages/tgui/interfaces/ChemHeater.tsx
@@ -1,6 +1,5 @@
-import { round, toFixed } from 'common/math';
-
-import { resolveAsset } from '../assets';
+import { round, toFixed } from '../../common/math';
+import { BooleanLike } from '../../common/react';
import { useBackend } from '../backend';
import {
AnimatedNumber,
@@ -16,48 +15,63 @@ import {
} from '../components';
import { COLORS } from '../constants';
import { Window } from '../layouts';
-import { BeakerContents } from './common/BeakerContents';
+import { Beaker, BeakerSectionDisplay } from './common/BeakerDisplay';
+
+type ActiveReaction = {
+ name: string;
+ danger: BooleanLike;
+ overheat: BooleanLike;
+ purityAlert: number;
+ quality: number;
+ inverse: number;
+ minPure: number;
+ reactedVol: number;
+ targetVol: number;
+};
+
+type Data = {
+ targetTemp: number;
+ isActive: BooleanLike;
+ upgradeLevel: number;
+ beaker: Beaker;
+ currentTemp: number;
+ activeReactions: ActiveReaction[];
+ isFlashing: number;
+ acidicBufferVol: number;
+ basicBufferVol: number;
+ dispenseVolume: number;
+};
export const ChemHeater = (props) => {
- const { act, data } = useBackend();
+ const { act, data } = useBackend();
const {
targetTemp,
isActive,
isFlashing,
- currentpH,
- isBeakerLoaded,
+ beaker,
currentTemp,
- beakerCurrentVolume,
- beakerMaxVolume,
acidicBufferVol,
basicBufferVol,
dispenseVolume,
upgradeLevel,
- tutorialMessage,
- beakerContents = [],
activeReactions = [],
} = data;
+ const isBeakerLoaded = beaker !== null;
+
return (
-
+
-
)}
- {tutorialMessage && (
-
-
- {tutorialMessage}
-
- )}
-
-
- {beakerCurrentVolume} / {beakerMaxVolume} units
-
- act('eject')}
- />
- >
- )
- }
- >
-
-
+
);
diff --git a/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx b/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx
index dc5b045d7f7..5c057755c5c 100644
--- a/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx
+++ b/tgui/packages/tgui/interfaces/common/BeakerDisplay.tsx
@@ -1,7 +1,12 @@
-import { BooleanLike } from 'common/react';
-
+import { BooleanLike } from '../../../common/react';
import { useBackend } from '../../backend';
-import { AnimatedNumber, Box, Button, LabeledList } from '../../components';
+import {
+ AnimatedNumber,
+ Box,
+ Button,
+ LabeledList,
+ Section,
+} from '../../components';
type BeakerReagent = {
name: string;
@@ -34,7 +39,9 @@ export const BeakerDisplay = (props: BeakerProps) => {
label="Beaker"
buttons={
!!beaker && (
- act('eject')} />
+ act('eject')}>
+ Eject
+
)
}
>
@@ -67,3 +74,43 @@ export const BeakerDisplay = (props: BeakerProps) => {
);
};
+
+export const BeakerSectionDisplay = (props: BeakerProps) => {
+ const { act } = useBackend();
+ const { beaker, replace_contents, title_label, showpH } = props;
+ const beakerContents = replace_contents || beaker?.contents || [];
+
+ return (
+
+
+ {beaker.currentVolume} / {beaker.maxVolume} units
+
+ act('eject')}>
+ Eject
+
+ >
+ )
+ }
+ >
+
+ {(!beaker && 'N/A') || (beakerContents.length === 0 && 'Nothing')}
+
+ {beakerContents.map((chemical) => (
+
+ units of{' '}
+ {chemical.name}
+
+ ))}
+ {beakerContents.length > 0 && !!showpH && (
+
+ pH:
+
+
+ )}
+
+ );
+};