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