mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-26 22:50:26 +01:00
[MIRROR] Chemical Reaction Tester/Debugger 2.0 (#26244)
* General maintenance for cryo chamber (#80873) The changes can be summarized into the following categories 1. **Qol**(Should not be a problem for feature freeze hopefully) - Adds balloon alerts for screwdriver & wrench acts, when inserting a beaker and other stuff - Adds extra examines for the beaker inside the cryo, how to open maintenance panel & other stuff. Most of the examines were not enclosed in `span_notice` - Adds screen tips for crowbar act, when inserting beaker into cryo etc - Cryo can now be pried open when there is no power with a crowbar so you can free someone stuck inside in case of an emergency - Cryo now auto turns on immediately without a 2 second delay & without the need for auto eject 2. **Fixes** - Cryo won't drop the beaker onto the floor when ejected, but will put it in the players hands as intended - Fixes #80699. The cryo was on `ABOVE_MOB_LAYER` which rendered on top of everything, Now its set to the default layer so mobs & other stuff now render on top of it - Loc checks were incorrect instead of doing `mob.loc == src`(which would fail if the mob was standing in the open cro) we now do `get_turf(mob) == get_turf(src)`(which works if the mob by anyway in on the turf of the machine) 3. **Code Improvements** - Get's rid of unused variables such as `sleep_factor`, `unconscious_factor` etc. Simply unused - Autodocs remaining variables and procs - Slightly touched on the UI shared by cryo, chem heater etc. Removes the `transfer_amounts` var that went unused in these cases 4. **Refactors** - Converted UI to typescript - Removes `check_nap_violations()` from `process()`. This proc checks if the occupant has an ID card & money in the account which makes no sense for cryo - Cryo will turn off if there is no occupant OR if there is no beaker - It no longer starts processing round start & only when the machine is turned on/off thus saving cpu - Removed old cryo icon states such `cell-open` , `cell-off` etc. These were used for the old cryo tube from years ago but are no longer needed now so the icon file size has been trimmed 🆑 qol: adds more examines, screentips & balloon alerts for cryo actions, cryo can be pried open with a crowbar when there is no power to free someone trapped inside qol: cryo auto turns on immediately without a 2 second delay & without needing auto eject mode on fix: ejecting beaker from cryotube will put it in the players hand & not drop it on the floor fix: mobs & other stuff now render on top of cryo tubes and not bottom of it fix: cryo checks for if the mob is on the same turf as cryo is now fixed, i.e. you can no longer close the machine on yourself code: removed unused vars from cryo, autodocs vars, removed unused `transfer_amounts` from some chem machinery ui refactor: removed unused icon states for cryo tube, cryo no longer processes round start and it's UI is now typescript /🆑 * Chemical Reaction Tester/Debugger 2.0 (#80987) ## About The Pull Request This machine belongs in runtime station and is used for debugging reactions. This PR fully reworks the chemical reaction tester or `/obj/machinery/chem_recipe_debug` from ground up. Here is a comparison between the old and the new system **1. Old System** - Played all reactions sequentially and very rapidly, stopping the machine would halt all reactions. It has no ability to replay an reaction, Play forward/backward through the long reaction list or more importantly pick a reaction at random - It would spawn a ton of beakers per reaction and create a ton of reagent waste - It would spam the chat with a lot of `say()`, overloading us with too much information - Cannot debug soup reactions which require solid ingredients - Code quality is just poor & UI is clunky **2. New System** - Gives you the option to play 1 reaction at a time, You can play next/play previous/pick a reaction from the list. - Gives you the explicit option to create a beaker from the stored reagents or even better reuse a beaker if you inserted 1 in - No chat spams - Now has support for soup reactions - Reaction display now matches that of chem heater for easy understandability - Better code & typescript UI https://github.com/tgstation/tgstation/assets/110812394/d2a73913-b7b2-4e2a-903c-c43fec1923f5 ## Changelog 🆑 refactor: chemical reaction tester in runtime station has been remastered from ground up. /🆑 --------- Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
This commit is contained in:
@@ -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 <b>[efficiency*100]%</b>.")
|
||||
|
||||
/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 <b>[efficiency * 100]</b>%.")
|
||||
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, "<span class='warning'>You can't access the maintenance panel while the pod is " \
|
||||
+ (on ? "active" : (occupant ? "full" : "open")) + "!</span>")
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 21 KiB |
@@ -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) => {
|
||||
|
||||
@@ -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 (
|
||||
<Section
|
||||
title="Reactions"
|
||||
buttons={
|
||||
<Flex>
|
||||
<Flex.Item color="label">
|
||||
<AnimatedNumber
|
||||
value={beaker.pH}
|
||||
format={(value) => 'pH: ' + round(value, 3)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<RoundGauge
|
||||
size={1.6}
|
||||
value={beaker.pH}
|
||||
minValue={0}
|
||||
maxValue={14}
|
||||
alertAfter={isFlashing}
|
||||
content={'test'}
|
||||
format={() => ''}
|
||||
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],
|
||||
}}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
{(activeReactions.length === 0 && (
|
||||
<Box color="label">No active reactions.</Box>
|
||||
)) || (
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell bold color="label">
|
||||
Reaction
|
||||
</Table.Cell>
|
||||
<Table.Cell bold color="label">
|
||||
{!highQualityDisplay ? 'Status' : 'Reaction quality'}
|
||||
</Table.Cell>
|
||||
<Table.Cell bold color="label">
|
||||
Target
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{activeReactions.map((reaction) => (
|
||||
<Table.Row key="reactions">
|
||||
<Table.Cell width={'60px'} color={reaction.danger && 'red'}>
|
||||
{reaction.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell width={'100px'} pr={'10px'}>
|
||||
{!highQualityDisplay ? (
|
||||
<Icon
|
||||
name={reaction.danger ? 'exclamation-triangle' : 'spinner'}
|
||||
color={reaction.danger && 'red'}
|
||||
spin={!reaction.danger}
|
||||
ml={2.5}
|
||||
/>
|
||||
) : (
|
||||
<RoundGauge
|
||||
size={1.3}
|
||||
value={reaction.quality}
|
||||
minValue={0}
|
||||
maxValue={1}
|
||||
alertAfter={reaction.purityAlert}
|
||||
content={'test'}
|
||||
format={(value) => ''}
|
||||
ml={5}
|
||||
ranges={{
|
||||
red: [0, reaction.minPure],
|
||||
orange: [reaction.minPure, reaction.inverse],
|
||||
yellow: [reaction.inverse, 0.8],
|
||||
green: [0.8, 1],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell width={'100px'}>
|
||||
{highDangerDisplay ? (
|
||||
<>
|
||||
{!!reaction.overheat && (
|
||||
<Icon
|
||||
name="thermometer-full"
|
||||
color="red"
|
||||
style={{ transform: 'scale(1.7)' }}
|
||||
mr="5px"
|
||||
/>
|
||||
)}
|
||||
<ProgressBar
|
||||
value={reaction.reactedVol}
|
||||
minValue={0}
|
||||
maxValue={reaction.targetVol}
|
||||
textAlign="center"
|
||||
width={8}
|
||||
color={reaction.overheat ? 'red' : 'label'}
|
||||
>
|
||||
{reaction.targetVol}u
|
||||
</ProgressBar>
|
||||
</>
|
||||
) : (
|
||||
<Box color={reaction.danger && 'red'} ml={2}>
|
||||
{reaction.targetVol}u
|
||||
</Box>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
<Table.Row />
|
||||
</Table>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChemHeater = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
@@ -208,125 +346,13 @@ export const ChemHeater = (props) => {
|
||||
</Table>
|
||||
</Section>
|
||||
{!!isBeakerLoaded && (
|
||||
<Section
|
||||
title="Reactions"
|
||||
buttons={
|
||||
<Flex>
|
||||
<Flex.Item color="label">
|
||||
<AnimatedNumber
|
||||
value={beaker.pH}
|
||||
format={(value) => 'pH: ' + round(value, 3)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<RoundGauge
|
||||
size={1.6}
|
||||
value={beaker.pH}
|
||||
minValue={0}
|
||||
maxValue={14}
|
||||
alertAfter={isFlashing}
|
||||
content={'test'}
|
||||
format={() => ''}
|
||||
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],
|
||||
}}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
{(activeReactions.length === 0 && (
|
||||
<Box color="label">No active reactions.</Box>
|
||||
)) || (
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell bold color="label">
|
||||
Reaction
|
||||
</Table.Cell>
|
||||
<Table.Cell bold color="label">
|
||||
{upgradeLevel < 4 ? 'Status' : 'Reaction quality'}
|
||||
</Table.Cell>
|
||||
<Table.Cell bold color="label">
|
||||
Target
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{activeReactions.map((reaction) => (
|
||||
<Table.Row key="reactions">
|
||||
<Table.Cell width={'60px'} color={reaction.danger && 'red'}>
|
||||
{reaction.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell width={'100px'} pr={'10px'}>
|
||||
{(upgradeLevel < 4 && (
|
||||
<Icon
|
||||
name={
|
||||
reaction.danger ? 'exclamation-triangle' : 'spinner'
|
||||
}
|
||||
color={reaction.danger && 'red'}
|
||||
spin={!reaction.danger}
|
||||
ml={2.5}
|
||||
/>
|
||||
)) || (
|
||||
<RoundGauge
|
||||
size={1.3}
|
||||
value={reaction.quality}
|
||||
minValue={0}
|
||||
maxValue={1}
|
||||
alertAfter={reaction.purityAlert}
|
||||
content={'test'}
|
||||
format={(value) => ''}
|
||||
ml={5}
|
||||
ranges={{
|
||||
red: [0, reaction.minPure],
|
||||
orange: [reaction.minPure, reaction.inverse],
|
||||
yellow: [reaction.inverse, 0.8],
|
||||
green: [0.8, 1],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell width={'100px'}>
|
||||
{(upgradeLevel > 2 && (
|
||||
<>
|
||||
{!!reaction.overheat && (
|
||||
<Icon
|
||||
name="thermometer-full"
|
||||
color="red"
|
||||
style={{ transform: 'scale(1.7)' }}
|
||||
mr="5px"
|
||||
/>
|
||||
)}
|
||||
<ProgressBar
|
||||
value={reaction.reactedVol}
|
||||
minValue={0}
|
||||
maxValue={reaction.targetVol}
|
||||
textAlign="center"
|
||||
width={8}
|
||||
color={reaction.overheat ? 'red' : 'label'}
|
||||
>
|
||||
{reaction.targetVol}u
|
||||
</ProgressBar>
|
||||
</>
|
||||
)) || (
|
||||
<Box color={reaction.danger && 'red'} ml={2}>
|
||||
{reaction.targetVol}u
|
||||
</Box>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
<Table.Row />
|
||||
</Table>
|
||||
)}
|
||||
</Section>
|
||||
<ReactionDisplay
|
||||
beaker={beaker}
|
||||
isFlashing={isFlashing}
|
||||
activeReactions={activeReactions}
|
||||
highQualityDisplay={upgradeLevel >= 4}
|
||||
highDangerDisplay={upgradeLevel >= 2}
|
||||
/>
|
||||
)}
|
||||
<BeakerSectionDisplay beaker={beaker} showpH={false} />
|
||||
</Window.Content>
|
||||
|
||||
@@ -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 (
|
||||
<Window width={450} height={850}>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title="Controls"
|
||||
buttons={
|
||||
<>
|
||||
<Button
|
||||
icon={beakerSpawn ? 'power-off' : 'times'}
|
||||
selected={beakerSpawn}
|
||||
content={'Spawn beaker'}
|
||||
onClick={() => act('beakerSpawn')}
|
||||
/>
|
||||
<Button
|
||||
icon={processAll ? 'power-off' : 'times'}
|
||||
selected={processAll}
|
||||
content={'All'}
|
||||
onClick={() => act('all')}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Reactions">
|
||||
<Button icon="plus" onClick={() => act('setTargetList')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Queued">
|
||||
{(processAll && <Box>All</Box>) || (
|
||||
<Box>
|
||||
{queuedReactions.length &&
|
||||
queuedReactions.map((entry) => entry.name + ', ')}
|
||||
</Box>
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Temp">
|
||||
{currentTemp}K
|
||||
<NumberInput
|
||||
width="65px"
|
||||
unit="K"
|
||||
step={10}
|
||||
stepPixelSize={3}
|
||||
value={round(targetTemp)}
|
||||
minValue={0}
|
||||
maxValue={1000}
|
||||
onDrag={(e, value) =>
|
||||
act('temperature', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon={forceTemp ? 'power-off' : 'times'}
|
||||
selected={forceTemp}
|
||||
content={'Force'}
|
||||
onClick={() => act('forceTemp')}
|
||||
/>
|
||||
<Button
|
||||
icon={minTemp ? 'power-off' : 'times'}
|
||||
selected={minTemp}
|
||||
content={'MinTemp'}
|
||||
onClick={() => act('minTemp')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Vol multi">
|
||||
<NumberInput
|
||||
width="65px"
|
||||
unit="x"
|
||||
step={1}
|
||||
stepPixelSize={3}
|
||||
value={round(targetVol)}
|
||||
minValue={1}
|
||||
maxValue={200}
|
||||
onDrag={(e, value) =>
|
||||
act('vol', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="pH">
|
||||
{currentpH}
|
||||
<NumberInput
|
||||
width="65px"
|
||||
step={0.1}
|
||||
stepPixelSize={3}
|
||||
value={targatpH}
|
||||
minValue={0}
|
||||
maxValue={14}
|
||||
onDrag={(e, value) =>
|
||||
act('pH', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon={forcepH ? 'power-off' : 'times'}
|
||||
selected={forcepH}
|
||||
content={'Force'}
|
||||
onClick={() => act('forcepH')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Index">
|
||||
{index} of {endIndex}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Start">
|
||||
<Button
|
||||
icon={processing ? 'power-off' : 'times'}
|
||||
selected={!!processing}
|
||||
content={'Start'}
|
||||
onClick={() => act('start')}
|
||||
/>
|
||||
<Button
|
||||
icon={processing ? 'times' : 'power-off'}
|
||||
color="red"
|
||||
content={'Stop'}
|
||||
onClick={() => act('stop')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Recipe edit">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label={editRecipeName ? editRecipeName : 'lookup'}
|
||||
>
|
||||
<Button
|
||||
icon={'flask'}
|
||||
color="purple"
|
||||
content={'Select recipe'}
|
||||
onClick={() => act('setEdit')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
{!!editRecipe && (
|
||||
<>
|
||||
<LabeledList.Item label="is_cold_recipe">
|
||||
<Button
|
||||
icon={editRecipeCold ? 'smile' : 'times'}
|
||||
color={editRecipeCold ? 'green' : 'red'}
|
||||
content={'Cold?'}
|
||||
onClick={() =>
|
||||
act('updateVar', {
|
||||
type: entry.name,
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
{editRecipe.map((entry) => (
|
||||
<LabeledList.Item label={entry.name} key={entry.name}>
|
||||
<NumberInput
|
||||
width="65px"
|
||||
step={1}
|
||||
stepPixelSize={3}
|
||||
value={entry.var}
|
||||
minValue={-9999}
|
||||
maxValue={9999}
|
||||
onDrag={(e, value) =>
|
||||
act('updateVar', {
|
||||
type: entry.name,
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<LabeledList.Item label="Export">
|
||||
<Button
|
||||
icon={'save'}
|
||||
color="green"
|
||||
content={'export'}
|
||||
onClick={() => act('export')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section
|
||||
title="Reactions"
|
||||
buttons={
|
||||
<Flex>
|
||||
<Flex.Item color="label">
|
||||
<AnimatedNumber
|
||||
value={currentpH}
|
||||
format={(value) => 'pH: ' + round(value, 3)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<RoundGauge
|
||||
size={1.6}
|
||||
value={currentpH}
|
||||
minValue={0}
|
||||
maxValue={14}
|
||||
alertAfter={isFlashing}
|
||||
content={'test'}
|
||||
format={() => ''}
|
||||
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],
|
||||
}}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
}
|
||||
>
|
||||
{(activeReactions.length === 0 && (
|
||||
<Box color="label">No active reactions.</Box>
|
||||
)) || (
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell bold color="label">
|
||||
Reaction
|
||||
</Table.Cell>
|
||||
<Table.Cell bold color="label">
|
||||
{'Reaction quality'}
|
||||
</Table.Cell>
|
||||
<Table.Cell bold color="label">
|
||||
Target
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{activeReactions &&
|
||||
activeReactions.map((reaction) => (
|
||||
<Table.Row key="reactions">
|
||||
<Table.Cell width={'60px'} color={reaction.danger && 'red'}>
|
||||
{reaction.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell width={'100px'} pr={'10px'}>
|
||||
<RoundGauge
|
||||
size={1.3}
|
||||
value={reaction.quality}
|
||||
minValue={0}
|
||||
maxValue={1}
|
||||
alertAfter={reaction.purityAlert}
|
||||
content={'test'}
|
||||
format={() => ''}
|
||||
ml={5}
|
||||
ranges={{
|
||||
red: [0, reaction.minPure],
|
||||
orange: [reaction.minPure, reaction.inverse],
|
||||
yellow: [reaction.inverse, 0.8],
|
||||
green: [0.8, 1],
|
||||
}}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell width={'70px'}>
|
||||
<ProgressBar
|
||||
value={reaction.reactedVol}
|
||||
minValue={0}
|
||||
maxValue={reaction.targetVol}
|
||||
textAlign={'center'}
|
||||
icon={reaction.overheat && 'thermometer-full'}
|
||||
width={7}
|
||||
color={reaction.overheat ? 'red' : 'label'}
|
||||
>
|
||||
{reaction.targetVol}u
|
||||
</ProgressBar>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
<Table.Row />
|
||||
</Table>
|
||||
)}
|
||||
</Section>
|
||||
<Section
|
||||
title="Chamber"
|
||||
buttons={<Box>{isActive ? 'Reacting' : 'Waiting'}</Box>}
|
||||
>
|
||||
{(chamberContents.length && (
|
||||
<BeakerContents beakerLoaded beakerContents={chamberContents} />
|
||||
)) || <Box>Nothing</Box>}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -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<Data>();
|
||||
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 (
|
||||
<Window width={500} height={600}>
|
||||
<Window.Content scrollable>
|
||||
<Section title="Controls">
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
key={'Environment'}
|
||||
selected={controlState === 'Environment'}
|
||||
onClick={() => setControlState('Environment')}
|
||||
>
|
||||
Environment
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
key={'Reactions'}
|
||||
selected={controlState === 'Reactions'}
|
||||
onClick={() => setControlState('Reactions')}
|
||||
>
|
||||
Reactions
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
key={'Editing'}
|
||||
selected={controlState === 'Editing'}
|
||||
onClick={() => setControlState('Editing')}
|
||||
>
|
||||
Edit Reactions
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
{controlState === 'Environment' && (
|
||||
<Section>
|
||||
<Stack vertical={false}>
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Temperature">
|
||||
<NumberInput
|
||||
width="65px"
|
||||
step={1}
|
||||
stepPixelSize={3}
|
||||
value={forced_temp}
|
||||
minValue={0}
|
||||
maxValue={1000}
|
||||
onDrag={(e, value) =>
|
||||
act('forced_temp', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label={
|
||||
<Box
|
||||
style={{
|
||||
transform: 'translate(0%, -50%)',
|
||||
}}
|
||||
>
|
||||
Temp Mode:
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Dropdown
|
||||
width="100%"
|
||||
selected={TEMP_MODES[temp_mode]}
|
||||
options={TEMP_MODES}
|
||||
onSelected={(value) =>
|
||||
act('temp_mode', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack vertical={false} mt="10px">
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label={<Box width="82px">PH:</Box>}>
|
||||
<NumberInput
|
||||
width="65px"
|
||||
step={1}
|
||||
stepPixelSize={3}
|
||||
value={forced_ph}
|
||||
minValue={0}
|
||||
maxValue={14}
|
||||
onDrag={(e, value) =>
|
||||
act('forced_ph', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item ml="0px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Force Ph">
|
||||
<Button.Checkbox
|
||||
checked={use_forced_ph}
|
||||
onClick={() => act('toggle_forced_ph')}
|
||||
>
|
||||
{use_forced_ph ? 'Disable' : 'Enable'}
|
||||
</Button.Checkbox>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack vertical={false} mt="10px">
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label={<Box width="82px">Purity:</Box>}>
|
||||
<NumberInput
|
||||
width="65px"
|
||||
step={0.01}
|
||||
stepPixelSize={3}
|
||||
value={forced_purity}
|
||||
minValue={0}
|
||||
maxValue={1}
|
||||
onDrag={(e, value) =>
|
||||
act('forced_purity', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item ml="10px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Force Purity">
|
||||
<Button.Checkbox
|
||||
checked={use_forced_purity}
|
||||
onClick={() => act('toggle_forced_purity')}
|
||||
>
|
||||
{use_forced_purity ? 'Disable' : 'Enable'}
|
||||
</Button.Checkbox>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Stack vertical={false} mt="10px">
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Volume Mulx">
|
||||
<NumberInput
|
||||
width="65px"
|
||||
step={1}
|
||||
stepPixelSize={3}
|
||||
value={volume_multiplier}
|
||||
minValue={1}
|
||||
maxValue={1000}
|
||||
unit="x"
|
||||
onDrag={(e, value) =>
|
||||
act('volume_multiplier', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
)}
|
||||
{controlState === 'Reactions' && (
|
||||
<Section>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Reagent">
|
||||
<Button
|
||||
color="green"
|
||||
onClick={() => act('pick_reaction')}
|
||||
>
|
||||
Select Reaction
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt="20px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Reaction">
|
||||
{current_reaction_name}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt="20px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label={
|
||||
<Box
|
||||
style={{
|
||||
transform: 'translate(0%, -50%)',
|
||||
}}
|
||||
>
|
||||
Direction:
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Dropdown
|
||||
width="35%"
|
||||
selected={REACTION_MODES[current_reaction_mode]}
|
||||
options={REACTION_MODES}
|
||||
disabled={current_reaction_name === 'N/A'}
|
||||
onSelected={(value) =>
|
||||
act('reaction_mode', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt="20px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label={<Box width="60px">Process:</Box>}>
|
||||
<Button
|
||||
color="green"
|
||||
icon="play"
|
||||
disabled={isReacting || current_reaction_name === 'N/A'}
|
||||
onClick={() => act('start_reaction')}
|
||||
>
|
||||
Play
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
)}
|
||||
{controlState === 'Editing' && (
|
||||
<Section>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Reaction">
|
||||
<Button
|
||||
color="green"
|
||||
icon="flask"
|
||||
onClick={() => act('edit_reaction')}
|
||||
>
|
||||
{editReaction?.name || 'Edit Reaction'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt="20px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label={
|
||||
<Box
|
||||
style={{
|
||||
transform: 'translate(0%, -50%)',
|
||||
width: '57px',
|
||||
}}
|
||||
>
|
||||
Param:
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<Dropdown
|
||||
width="40%"
|
||||
selected={editReaction?.editVar || REACTION_VARS[1]}
|
||||
options={REACTION_VARS}
|
||||
onSelected={(value) =>
|
||||
act('edit_var', { target: value })
|
||||
}
|
||||
disabled={editReaction === null}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt="20px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label={<Box width="57px">Value:</Box>}>
|
||||
<NumberInput
|
||||
width="65px"
|
||||
step={0.1}
|
||||
stepPixelSize={3}
|
||||
value={editReaction?.editValue || 0}
|
||||
minValue={0}
|
||||
maxValue={1000}
|
||||
disabled={editReaction === null}
|
||||
onDrag={(e, value) =>
|
||||
act('edit_value', {
|
||||
target: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
color="green"
|
||||
icon="sync"
|
||||
tooltip="Reset Value"
|
||||
disabled={editReaction === null}
|
||||
onClick={() => act('reset_value')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item mt="20px">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label={<Box width="57px">Export:</Box>}>
|
||||
<Button
|
||||
color="green"
|
||||
icon="save"
|
||||
onClick={() => act('export')}
|
||||
disabled={editReaction === null}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
)}
|
||||
</Section>
|
||||
{beaker && (
|
||||
<Section title="Variables">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Temperature">
|
||||
{beaker.currentTemp}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Purity">
|
||||
{beaker.purity}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
)}
|
||||
{beaker && (
|
||||
<ReactionDisplay
|
||||
beaker={beaker}
|
||||
isFlashing={isFlashing}
|
||||
activeReactions={activeReactions}
|
||||
highQualityDisplay
|
||||
highDangerDisplay
|
||||
/>
|
||||
)}
|
||||
<BeakerSectionDisplay
|
||||
title_label="Internal Buffer"
|
||||
beaker={beaker}
|
||||
showpH={false}
|
||||
/>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -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 (
|
||||
<Window width={400} height={550}>
|
||||
<Window.Content scrollable>
|
||||
<CryoContent />
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const CryoContent = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
return (
|
||||
<>
|
||||
<Section title="Occupant">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Occupant">
|
||||
{data.occupant.name || 'No Occupant'}
|
||||
</LabeledList.Item>
|
||||
{!!data.hasOccupant && (
|
||||
<>
|
||||
<LabeledList.Item label="State" color={data.occupant.statstate}>
|
||||
{data.occupant.stat}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Temperature"
|
||||
color={data.occupant.temperaturestatus}
|
||||
>
|
||||
<AnimatedNumber value={data.occupant.bodyTemperature} />
|
||||
{' K'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Health">
|
||||
<ProgressBar
|
||||
value={data.occupant.health / data.occupant.maxHealth}
|
||||
color={data.occupant.health > 0 ? 'good' : 'average'}
|
||||
>
|
||||
<AnimatedNumber value={data.occupant.health} />
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
{damageTypes.map((damageType) => (
|
||||
<LabeledList.Item key={damageType.id} label={damageType.label}>
|
||||
<ProgressBar value={data.occupant[damageType.type] / 100}>
|
||||
<AnimatedNumber value={data.occupant[damageType.type]} />
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Cell">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Power">
|
||||
<Button
|
||||
icon={data.isOperating ? 'power-off' : 'times'}
|
||||
disabled={data.isOpen}
|
||||
onClick={() => act('power')}
|
||||
color={data.isOperating && 'green'}
|
||||
>
|
||||
{data.isOperating ? 'On' : 'Off'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Temperature">
|
||||
<AnimatedNumber value={data.cellTemperature} /> K
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Door">
|
||||
<Button
|
||||
icon={data.isOpen ? 'unlock' : 'lock'}
|
||||
onClick={() => act('door')}
|
||||
content={data.isOpen ? 'Open' : 'Closed'}
|
||||
/>
|
||||
<Button
|
||||
icon={data.autoEject ? 'sign-out-alt' : 'sign-in-alt'}
|
||||
onClick={() => act('autoeject')}
|
||||
content={data.autoEject ? 'Auto' : 'Manual'}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section
|
||||
title="Beaker"
|
||||
buttons={
|
||||
<Button
|
||||
icon="eject"
|
||||
disabled={!data.isBeakerLoaded}
|
||||
onClick={() => act('ejectbeaker')}
|
||||
content="Eject"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<BeakerContents
|
||||
beakerLoaded={data.isBeakerLoaded}
|
||||
beakerContents={data.beakerContents}
|
||||
/>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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<Data>();
|
||||
const { occupant, isOperating, isOpen } = data;
|
||||
|
||||
return (
|
||||
<Window width={400} height={550}>
|
||||
<Window.Content scrollable>
|
||||
<Section title="Occupant">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Occupant">
|
||||
{occupant?.name || 'No Occupant'}
|
||||
</LabeledList.Item>
|
||||
{!!occupant && (
|
||||
<>
|
||||
<LabeledList.Item
|
||||
label="State"
|
||||
color={stat_to_color[occupant.stat]}
|
||||
>
|
||||
{occupant.stat}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Temperature"
|
||||
color={occupant.bodyTemperature < data.T0C ? 'good' : 'bad'} // Green if the mob can actually be healed by cryoxadone.
|
||||
>
|
||||
<AnimatedNumber value={round(occupant.bodyTemperature, 0)} />
|
||||
{' K'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Health">
|
||||
<ProgressBar
|
||||
value={round(occupant.health / occupant.maxHealth, 2)}
|
||||
color={occupant.health > 0 ? 'good' : 'average'}
|
||||
>
|
||||
<AnimatedNumber value={round(occupant.health, 0)} />
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
{damageTypes.map((damageType) => (
|
||||
<LabeledList.Item
|
||||
key={damageType.type}
|
||||
label={damageType.label}
|
||||
>
|
||||
<ProgressBar
|
||||
value={round(data.occupant[damageType.type] / 100, 2)}
|
||||
>
|
||||
<AnimatedNumber
|
||||
value={round(data.occupant[damageType.type], 0)}
|
||||
/>
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Cell">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Power">
|
||||
<Button
|
||||
icon={isOperating ? 'power-off' : 'times'}
|
||||
disabled={isOpen}
|
||||
onClick={() => act('power')}
|
||||
color={isOperating && 'green'}
|
||||
>
|
||||
{isOperating ? 'On' : 'Off'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Temperature">
|
||||
<AnimatedNumber value={round(data.cellTemperature, 0)} /> K
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Door">
|
||||
<Button
|
||||
icon={isOpen ? 'unlock' : 'lock'}
|
||||
onClick={() => act('door')}
|
||||
>
|
||||
{isOpen ? 'Open' : 'Closed'}
|
||||
</Button>
|
||||
<Button
|
||||
icon={data.autoEject ? 'sign-out-alt' : 'sign-in-alt'}
|
||||
onClick={() => act('autoeject')}
|
||||
>
|
||||
{data.autoEject ? 'Auto' : 'Manual'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<BeakerSectionDisplay beaker={data.beaker} showpH={false} />
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -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) => {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { AnimatedNumber, Box } from '../../components';
|
||||
|
||||
export const BeakerContents = (props) => {
|
||||
const { beakerLoaded, beakerContents } = props;
|
||||
return (
|
||||
<Box>
|
||||
{(!beakerLoaded && <Box color="label">No beaker loaded.</Box>) ||
|
||||
(beakerContents.length === 0 && (
|
||||
<Box color="label">Beaker is empty.</Box>
|
||||
))}
|
||||
{beakerContents.map((chemical) => (
|
||||
<Box key={chemical.name} color="label">
|
||||
<AnimatedNumber initial={0} value={chemical.volume} />
|
||||
{' units of ' + chemical.name}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -15,7 +15,6 @@ type BeakerReagent = {
|
||||
|
||||
export type Beaker = {
|
||||
maxVolume: number;
|
||||
transferAmounts: number[];
|
||||
pH: number;
|
||||
currentVolume: number;
|
||||
contents: BeakerReagent[];
|
||||
|
||||
Reference in New Issue
Block a user