initial commit - cross reference with 5th port - obviously has compile errors

This commit is contained in:
LetterJay
2016-07-03 02:17:19 -05:00
commit 35a1723e98
4355 changed files with 2221257 additions and 0 deletions
@@ -0,0 +1,179 @@
/atom/proc/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
return null
/turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
return
/turf/open/hotspot_expose(exposed_temperature, exposed_volume, soh)
var/datum/gas_mixture/air_contents = return_air()
if(!air_contents)
return 0
var/oxy = air_contents.gases["o2"] ? air_contents.gases["o2"][MOLES] : 0
var/tox = air_contents.gases["plasma"] ? air_contents.gases["plasma"][MOLES] : 0
if(active_hotspot)
if(soh)
if(tox > 0.5 && oxy > 0.5)
if(active_hotspot.temperature < exposed_temperature)
active_hotspot.temperature = exposed_temperature
if(active_hotspot.volume < exposed_volume)
active_hotspot.volume = exposed_volume
return 1
var/igniting = 0
if((exposed_temperature > PLASMA_MINIMUM_BURN_TEMPERATURE) && tox > 0.5)
igniting = 1
if(igniting)
if(oxy < 0.5 || tox < 0.5)
return 0
active_hotspot = PoolOrNew(/obj/effect/hotspot, src)
active_hotspot.temperature = exposed_temperature
active_hotspot.volume = exposed_volume
active_hotspot.just_spawned = (current_cycle < SSair.times_fired)
//remove just_spawned protection if no longer processing this cell
SSair.add_to_active(src, 0)
return igniting
//This is the icon for fire on turfs, also helps for nurturing small fires until they are full tile
/obj/effect/hotspot
anchored = 1
mouse_opacity = 0
unacidable = 1//So you can't melt fire with acid.
icon = 'icons/effects/fire.dmi'
icon_state = "1"
layer = ABOVE_OPEN_TURF_LAYER
luminosity = 3
var/volume = 125
var/temperature = FIRE_MINIMUM_TEMPERATURE_TO_EXIST
var/just_spawned = 1
var/bypassing = 0
/obj/effect/hotspot/New()
..()
SSair.hotspots += src
perform_exposure()
setDir(pick(cardinal))
air_update_turf()
/obj/effect/hotspot/proc/perform_exposure()
var/turf/open/location = loc
if(!istype(location) || !(location.air))
return 0
if(volume > CELL_VOLUME*0.95)
bypassing = 1
else
bypassing = 0
if(bypassing)
if(!just_spawned)
volume = location.air.fuel_burnt*FIRE_GROWTH_RATE
temperature = location.air.temperature
else
var/datum/gas_mixture/affected = location.air.remove_ratio(volume/location.air.volume)
affected.temperature = temperature
affected.react()
temperature = affected.temperature
volume = affected.fuel_burnt*FIRE_GROWTH_RATE
location.assume_air(affected)
for(var/A in loc)
var/atom/item = A
if(item && item != src) // It's possible that the item is deleted in temperature_expose
item.fire_act(null, temperature, volume)
return 0
/obj/effect/hotspot/process()
if(just_spawned)
just_spawned = 0
return 0
var/turf/open/location = loc
if(!istype(location))
qdel(src)
return
if(location.excited_group)
location.excited_group.reset_cooldowns()
if((temperature < FIRE_MINIMUM_TEMPERATURE_TO_EXIST) || (volume <= 1))
qdel(src)
return
if(!(location.air) || !location.air.gases["plasma"] || !location.air.gases["o2"] || location.air.gases["plasma"][MOLES] < 0.5 || location.air.gases["o2"][MOLES] < 0.5)
qdel(src)
return
perform_exposure()
if(bypassing)
icon_state = "3"
location.burn_tile()
//Possible spread due to radiated heat
if(location.air.temperature > FIRE_MINIMUM_TEMPERATURE_TO_SPREAD)
var/radiated_temperature = location.air.temperature*FIRE_SPREAD_RADIOSITY_SCALE
for(var/t in location.atmos_adjacent_turfs)
var/turf/open/T = t
if(T.active_hotspot)
T.hotspot_expose(radiated_temperature, CELL_VOLUME/4)
else
if(volume > CELL_VOLUME*0.4)
icon_state = "2"
else
icon_state = "1"
if(temperature > location.max_fire_temperature_sustained)
location.max_fire_temperature_sustained = temperature
if(location.heat_capacity && temperature > location.heat_capacity)
location.to_be_destroyed = 1
/*if(prob(25))
location.ReplaceWithSpace()
return 0*/
return 1
/obj/effect/hotspot/Destroy()
SetLuminosity(0)
SSair.hotspots -= src
DestroyTurf()
if(istype(loc, /turf))
var/turf/open/T = loc
if(T.active_hotspot == src)
T.active_hotspot = null
loc = null
..()
return QDEL_HINT_PUTINPOOL
/obj/effect/hotspot/proc/DestroyTurf()
if(istype(loc, /turf))
var/turf/T = loc
if(T.to_be_destroyed)
var/chance_of_deletion
if (T.heat_capacity) //beware of division by zero
chance_of_deletion = T.max_fire_temperature_sustained / T.heat_capacity * 8 //there is no problem with prob(23456), min() was redundant --rastaf0
else
chance_of_deletion = 100
if(prob(chance_of_deletion))
T.ChangeTurf(T.baseturf)
else
T.to_be_destroyed = 0
T.max_fire_temperature_sustained = 0
/obj/effect/hotspot/Crossed(mob/living/L)
..()
if(isliving(L))
L.fire_act()
@@ -0,0 +1,124 @@
/turf/proc/CanAtmosPass(turf/T)
/turf/closed/CanAtmosPass(turf/T)
return 0
/turf/open/CanAtmosPass(turf/T)
var/R
if(blocks_air || T.blocks_air)
R = 1
for(var/obj/O in contents+T.contents)
var/turf/other = (O.loc == src ? T : src)
if(!O.CanAtmosPass(other))
R = 1
if(O.BlockSuperconductivity()) //the direction and open/closed are already checked on CanAtmosPass() so there are no arguments
var/D = get_dir(src, T)
atmos_supeconductivity |= D
D = get_dir(T, src)
T.atmos_supeconductivity |= D
return 0 //no need to keep going, we got all we asked
atmos_supeconductivity &= ~get_dir(src, T)
T.atmos_supeconductivity &= ~get_dir(T, src)
return !R
/atom/movable/proc/CanAtmosPass()
return 1
/atom/proc/CanPass(atom/movable/mover, turf/target, height=1.5)
return (!density || !height)
/turf/CanPass(atom/movable/mover, turf/target, height=1.5)
if(!target) return 0
if(istype(mover)) // turf/Enter(...) will perform more advanced checks
return !density
else // Now, doing more detailed checks for air movement and air group formation
if(target.blocks_air||blocks_air)
return 0
for(var/obj/obstacle in src)
if(!obstacle.CanPass(mover, target, height))
return 0
for(var/obj/obstacle in target)
if(!obstacle.CanPass(mover, src, height))
return 0
return 1
/atom/movable/proc/BlockSuperconductivity() // objects that block air and don't let superconductivity act. Only firelocks atm.
return 0
/turf/proc/CalculateAdjacentTurfs()
for(var/direction in cardinal)
var/turf/open/T = get_step(src, direction)
if(!istype(T))
continue
if(CanAtmosPass(T))
atmos_adjacent_turfs |= T
T.atmos_adjacent_turfs |= src
else
atmos_adjacent_turfs -= T
T.atmos_adjacent_turfs -= src
//returns a list of adjacent turfs that can share air with this one.
//alldir includes adjacent diagonal tiles that can share
// air with both of the related adjacent cardinal tiles
/turf/proc/GetAtmosAdjacentTurfs(alldir = 0)
var/adjacent_turfs = atmos_adjacent_turfs.Copy()
if (!alldir)
return adjacent_turfs
var/turf/curloc = src
for (var/direction in diagonals)
var/matchingDirections = 0
var/turf/S = get_step(curloc, direction)
for (var/checkDirection in cardinal)
var/turf/checkTurf = get_step(S, checkDirection)
if(!(checkTurf in S.atmos_adjacent_turfs))
continue
if (checkTurf in adjacent_turfs)
matchingDirections++
if (matchingDirections >= 2)
adjacent_turfs += S
break
return adjacent_turfs
/atom/movable/proc/air_update_turf(command = 0)
if(!istype(loc,/turf) && command)
return
var/turf/T = get_turf(loc)
T.air_update_turf(command)
/turf/proc/air_update_turf(command = 0)
if(command)
CalculateAdjacentTurfs()
SSair.add_to_active(src,command)
/atom/movable/proc/move_update_air(turf/T)
if(istype(T,/turf))
T.air_update_turf(1)
air_update_turf(1)
/atom/movable/proc/atmos_spawn_air(text) //because a lot of people loves to copy paste awful code lets just make a easy proc to spawn your plasma fires
var/turf/open/T = get_turf(src)
if(!istype(T))
return
T.atmos_spawn_air(text)
/turf/open/proc/atmos_spawn_air(text)
if(!text || !air)
return
var/datum/gas_mixture/G = new
G.parse_gas_string(text)
air.merge(G)
SSair.add_to_active(src, 0)
@@ -0,0 +1,425 @@
/turf
//used for temperature calculations
var/thermal_conductivity = 0.05
var/heat_capacity = 1
var/temperature_archived
//list of open turfs adjacent to us
var/list/atmos_adjacent_turfs = list()
//bitfield of dirs in which we are superconducitng
var/atmos_supeconductivity = 0
//used to determine whether we should archive
var/archived_cycle = 0
var/current_cycle = 0
//used for mapping and for breathing while in walls (because that's a thing that needs to be accounted for...)
//string parsed by /datum/gas/proc/copy_from_turf
var/initial_gas_mix = "o2=22;n2=82;TEMP=293.15"
//approximation of MOLES_O2STANDARD and MOLES_N2STANDARD pending byond allowing constant expressions to be embedded in constant strings
// If someone will place 0 of some gas there, SHIT WILL BREAK. Do not do that.
/turf/open
//used for spacewind
var/pressure_difference = 0
var/pressure_direction = 0
var/datum/excited_group/excited_group
var/excited = 0
var/recently_active = 0
var/datum/gas_mixture/air
var/obj/effect/hotspot/active_hotspot
var/list/atmos_overlay_types = list() //gas IDs of current active gas overlays
/turf/open/New()
..()
if(!blocks_air)
air = new
air.copy_from_turf(src)
/turf/open/Destroy()
if(active_hotspot)
qdel(active_hotspot)
active_hotspot = null
// Adds the adjacent turfs to the current atmos processing
for(var/T in atmos_adjacent_turfs)
SSair.add_to_active(T)
return ..()
/////////////////GAS MIXTURE PROCS///////////////////
/turf/open/assume_air(datum/gas_mixture/giver) //use this for machines to adjust air
if(!giver)
return 0
air.merge(giver)
update_visuals()
return 1
/turf/open/remove_air(amount)
var/datum/gas_mixture/ours = return_air()
var/datum/gas_mixture/removed = ours.remove(amount)
update_visuals()
return removed
/turf/open/proc/copy_air_with_tile(turf/open/T)
if(istype(T))
air.copy_from(T.air)
/turf/open/proc/copy_air(datum/gas_mixture/copy)
if(copy)
air.copy_from(copy)
/turf/return_air()
var/datum/gas_mixture/GM = new
GM.copy_from_turf(src)
return GM
/turf/open/return_air()
return air
/turf/temperature_expose()
if(temperature > heat_capacity)
to_be_destroyed = 1
/turf/proc/archive()
temperature_archived = temperature
/turf/open/archive()
air.archive()
archived_cycle = SSair.times_fired
..()
/////////////////////////GAS OVERLAYS//////////////////////////////
/turf/open/proc/update_visuals()
var/list/new_overlay_types = tile_graphic()
for(var/overlay in atmos_overlay_types-new_overlay_types) //doesn't remove overlays that would only be added
overlays -= overlay
atmos_overlay_types -= overlay
for(var/overlay in new_overlay_types-atmos_overlay_types) //doesn't add overlays that already exist
add_overlay(overlay)
atmos_overlay_types = new_overlay_types
/turf/open/proc/tile_graphic()
. = new /list
var/list/gases = air.gases
for(var/id in gases)
var/gas = gases[id]
if(gas[GAS_META][META_GAS_OVERLAY] && gas[MOLES] > gas[GAS_META][META_GAS_MOLES_VISIBLE])
. += gas[GAS_META][META_GAS_OVERLAY]
/////////////////////////////SIMULATION///////////////////////////////////
/turf/proc/process_cell(fire_count)
SSair.remove_from_active(src)
/turf/open/process_cell(fire_count)
if(archived_cycle < fire_count) //archive self if not already done
archive()
current_cycle = fire_count
var/remove = 1 //set by non simulated turfs who are sharing with this turf
//cache for sanic speed
var/list/adjacent_turfs = atmos_adjacent_turfs
var/datum/excited_group/our_excited_group = excited_group
var/adjacent_turfs_length = adjacent_turfs.len
for(var/t in adjacent_turfs)
var/turf/open/enemy_tile = t
if(fire_count > enemy_tile.current_cycle)
enemy_tile.archive()
/******************* GROUP HANDLING START *****************************************************************/
if(enemy_tile.excited)
//cache for sanic speed
var/datum/excited_group/enemy_excited_group = enemy_tile.excited_group
if(our_excited_group)
if(enemy_excited_group)
if(our_excited_group != enemy_excited_group)
//combine groups (this also handles updating the excited_group var of all involved turfs)
our_excited_group.merge_groups(enemy_excited_group)
our_excited_group = excited_group //update our cache
share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
else
if((recently_active == 1 && enemy_tile.recently_active == 1) || air.compare(enemy_tile.air))
our_excited_group.add_turf(enemy_tile) //add enemy to our group
share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
else
if(enemy_excited_group)
if((recently_active == 1 && enemy_tile.recently_active == 1) || air.compare(enemy_tile.air))
enemy_excited_group.add_turf(src) //join self to enemy group
our_excited_group = excited_group //update our cache
share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
else
if((recently_active == 1 && enemy_tile.recently_active == 1) || air.compare(enemy_tile.air))
var/datum/excited_group/EG = new //generate new group
EG.add_turf(src)
EG.add_turf(enemy_tile)
our_excited_group = excited_group //update our cache
share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
else
if(air.compare(enemy_tile.air)) //compare if
SSair.add_to_active(enemy_tile) //excite enemy
if(our_excited_group)
excited_group.add_turf(enemy_tile) //add enemy to group
else
var/datum/excited_group/EG = new //generate new group
EG.add_turf(src)
EG.add_turf(enemy_tile)
our_excited_group = excited_group //update our cache
share_air(enemy_tile, fire_count, adjacent_turfs_length) //share
/******************* GROUP HANDLING FINISH *********************************************************************/
air.react()
update_visuals()
if(air.temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
hotspot_expose(air.temperature, CELL_VOLUME)
for(var/atom/movable/item in src)
item.temperature_expose(air, air.temperature, CELL_VOLUME)
temperature_expose(air, air.temperature, CELL_VOLUME)
if(air.temperature > MINIMUM_TEMPERATURE_START_SUPERCONDUCTION)
if(consider_superconductivity(starting = 1))
remove = 0
if(!our_excited_group && remove == 1)
SSair.remove_from_active(src)
/turf/open/proc/share_air(turf/open/T, fire_count, adjacent_turfs_length)
if(T.current_cycle < fire_count)
var/difference = air.share(T.air, adjacent_turfs_length)
if(difference)
if(difference > 0)
consider_pressure_difference(T, difference)
else
T.consider_pressure_difference(src, -difference)
last_share_check()
//////////////////////////SPACEWIND/////////////////////////////
/turf/open/proc/consider_pressure_difference(turf/T, difference)
SSair.high_pressure_delta |= src
if(difference > pressure_difference)
pressure_direction = get_dir(src, T)
pressure_difference = difference
/turf/open/proc/last_share_check()
if(air.last_share > MINIMUM_AIR_TO_SUSPEND)
excited_group.reset_cooldowns()
else if(air.last_share > MINIMUM_MOLES_DELTA_TO_MOVE)
excited_group.dismantle_cooldown = 0
/turf/open/proc/high_pressure_movements()
for(var/atom/movable/M in src)
M.experience_pressure_difference(pressure_difference, pressure_direction)
/atom/movable/var/pressure_resistance = 5
/atom/movable/var/last_high_pressure_movement_air_cycle = 0
/atom/movable/proc/experience_pressure_difference(pressure_difference, direction)
set waitfor = 0
. = 0
if(!anchored && !pulledby)
. = 1
if(pressure_difference > pressure_resistance && last_high_pressure_movement_air_cycle < SSair.times_fired)
last_high_pressure_movement_air_cycle = SSair.times_fired
step(src, direction)
///////////////////////////EXCITED GROUPS/////////////////////////////
/datum/excited_group
var/list/turf_list = list()
var/breakdown_cooldown = 0
var/dismantle_cooldown = 0
/datum/excited_group/New()
SSair.excited_groups += src
/datum/excited_group/proc/add_turf(turf/open/T)
turf_list += T
T.excited_group = src
T.recently_active = 1
reset_cooldowns()
/datum/excited_group/proc/merge_groups(datum/excited_group/E)
if(turf_list.len > E.turf_list.len)
SSair.excited_groups -= E
for(var/t in E.turf_list)
var/turf/open/T = t
T.excited_group = src
turf_list += T
reset_cooldowns()
else
SSair.excited_groups -= src
for(var/t in turf_list)
var/turf/open/T = t
T.excited_group = E
E.turf_list += T
E.reset_cooldowns()
/datum/excited_group/proc/reset_cooldowns()
breakdown_cooldown = 0
dismantle_cooldown = 0
//argument is so world start can clear out any turf differences quickly.
/datum/excited_group/proc/self_breakdown(space_is_all_consuming = 0)
var/datum/gas_mixture/A = new
//make local for sanic speed
var/list/A_gases = A.gases
var/list/turf_list = src.turf_list
var/turflen = turf_list.len
var/space_in_group = 0
for(var/t in turf_list)
var/turf/open/T = t
if (space_is_all_consuming && !space_in_group && istype(T.air, /datum/gas_mixture/space))
space_in_group = 1
qdel(A)
A = new/datum/gas_mixture/space()
A.merge(T.air)
for(var/id in A_gases)
A_gases[id][MOLES] = A_gases[id][MOLES]/turflen
for(var/t in turf_list)
var/turf/open/T = t
T.air.copy_from(A)
T.update_visuals()
breakdown_cooldown = 0
/datum/excited_group/proc/dismantle()
for(var/t in turf_list)
var/turf/open/T = t
T.excited = 0
T.recently_active = 0
T.excited_group = null
SSair.active_turfs -= T
garbage_collect()
/datum/excited_group/proc/garbage_collect()
for(var/t in turf_list)
var/turf/open/T = t
T.excited_group = null
turf_list.Cut()
SSair.excited_groups -= src
////////////////////////SUPERCONDUCTIVITY/////////////////////////////
/turf/proc/conductivity_directions()
if(archived_cycle < SSair.times_fired)
archive()
return NORTH|SOUTH|EAST|WEST
/turf/open/conductivity_directions()
if(blocks_air)
return ..()
for(var/direction in cardinal)
var/turf/T = get_step(src, direction)
if(!(T in atmos_adjacent_turfs) && !(atmos_supeconductivity & direction))
. |= direction
/turf/proc/neighbor_conduct_with_src(turf/open/other)
if(!other.blocks_air) //Open but neighbor is solid
other.temperature_share_open_to_solid(src)
else //Both tiles are solid
other.share_temperature_mutual_solid(src, thermal_conductivity)
temperature_expose(null, temperature, null)
/turf/open/neighbor_conduct_with_src(turf/other)
if(blocks_air)
..()
return
if(!other.blocks_air) //Both tiles are open
var/turf/open/T = other
T.air.temperature_share(air, WINDOW_HEAT_TRANSFER_COEFFICIENT)
else //Solid but neighbor is open
temperature_share_open_to_solid(other)
SSair.add_to_active(src, 0)
/turf/proc/super_conduct()
var/conductivity_directions = conductivity_directions()
if(conductivity_directions)
//Conduct with tiles around me
for(var/direction in cardinal)
if(conductivity_directions & direction)
var/turf/neighbor = get_step(src,direction)
if(!neighbor.thermal_conductivity)
continue
if(neighbor.archived_cycle < SSair.times_fired)
neighbor.archive()
neighbor.neighbor_conduct_with_src(src)
neighbor.consider_superconductivity()
radiate_to_spess()
finish_superconduction()
/turf/proc/finish_superconduction(temp = temperature)
//Make sure still hot enough to continue conducting heat
if(temp < MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION)
SSair.active_super_conductivity -= src
return 0
/turf/open/finish_superconduction()
//Conduct with air on my tile if I have it
if(!blocks_air)
temperature = air.temperature_share(null, thermal_conductivity, temperature, heat_capacity)
..((blocks_air ? temperature : air.temperature))
/turf/proc/consider_superconductivity()
if(!thermal_conductivity)
return 0
SSair.active_super_conductivity |= src
return 1
/turf/open/consider_superconductivity(starting)
if(air.temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
return 0
if(air.heat_capacity() < M_CELL_WITH_RATIO) // Was: MOLES_CELLSTANDARD*0.1*0.05 Since there are no variables here we can make this a constant.
return 0
return ..()
/turf/closed/consider_superconductivity(starting)
if(temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
return 0
return ..()
/turf/proc/radiate_to_spess() //Radiate excess tile heat to space
if(temperature > T0C) //Considering 0 degC as te break even point for radiation in and out
var/delta_temperature = (temperature_archived - TCMB) //hardcoded space temperature
if((heat_capacity > 0) && (abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER))
var/heat = thermal_conductivity*delta_temperature* \
(heat_capacity*700000/(heat_capacity+700000)) //700000 is the heat_capacity from a space turf, hardcoded here
temperature -= heat/heat_capacity
/turf/open/proc/temperature_share_open_to_solid(turf/sharer)
sharer.temperature = air.temperature_share(null, sharer.thermal_conductivity, sharer.temperature, sharer.heat_capacity)
/turf/proc/share_temperature_mutual_solid(turf/sharer, conduction_coefficient) //to be understood
var/delta_temperature = (temperature_archived - sharer.temperature_archived)
if(abs(delta_temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER && heat_capacity && sharer.heat_capacity)
var/heat = conduction_coefficient*delta_temperature* \
(heat_capacity*sharer.heat_capacity/(heat_capacity+sharer.heat_capacity))
temperature -= heat/heat_capacity
sharer.temperature += heat/sharer.heat_capacity
@@ -0,0 +1,562 @@
/*
What are the archived variables for?
Calculations are done using the archived variables with the results merged into the regular variables.
This prevents race conditions that arise based on the order of tile processing.
*/
#define MINIMUM_HEAT_CAPACITY 0.0003
#define QUANTIZE(variable) (round(variable,0.0000001))/*I feel the need to document what happens here. Basically this is used to catch most rounding errors, however it's previous value made it so that
once gases got hot enough, most procedures wouldnt occur due to the fact that the mole counts would get rounded away. Thus, we lowered it a few orders of magnititude */
var/list/meta_gas_info = meta_gas_list() //see ATMOSPHERICS/gas_types.dm
var/list/gaslist_cache = null
/proc/gaslist(id)
var/list/cached_gas
//only instantiate the first time it's needed
if(!gaslist_cache)
gaslist_cache = new(meta_gas_info.len)
//only setup the individual lists the first time they're needed
if(!gaslist_cache[id])
if(!meta_gas_info[id])
CRASH("Gas [id] does not exist!")
cached_gas = new(3)
gaslist_cache[id] = cached_gas
cached_gas[MOLES] = 0
cached_gas[ARCHIVE] = 0
cached_gas[GAS_META] = meta_gas_info[id]
else
cached_gas = gaslist_cache[id]
//Copy() it because only GAS_META is static
return cached_gas.Copy()
/datum/gas_mixture
var/list/gases
var/temperature //kelvins
var/tmp/temperature_archived
var/volume //liters
var/last_share
var/tmp/fuel_burnt
/datum/gas_mixture/New(volume = CELL_VOLUME)
..()
gases = new
temperature = 0
temperature_archived = 0
src.volume = volume
last_share = 0
fuel_burnt = 0
//listmos procs
//assert_gas(gas_id) - used to guarantee that the gas list for this id exists.
//Must be used before adding to a gas. May be used before reading from a gas.
/datum/gas_mixture/proc/assert_gas(gas_id)
var/cached_gases = gases
if(cached_gases[gas_id])
return
cached_gases[gas_id] = gaslist(gas_id)
//assert_gases(args) - shorthand for calling assert_gas() once for each gas type.
/datum/gas_mixture/proc/assert_gases()
for(var/id in args)
assert_gas(id)
//add_gas(gas_id) - similar to assert_gas(), but does not check for an existing
//gas list for this id. This can clobber existing gases.
//Used instead of assert_gas() when you know the gas does not exist. Faster than assert_gas().
/datum/gas_mixture/proc/add_gas(gas_id)
gases[gas_id] = gaslist(gas_id)
//add_gases(args) - shorthand for calling add_gas() once for each gas_type.
/datum/gas_mixture/proc/add_gases()
for(var/id in args)
add_gas(id)
//garbage_collect() - removes any gas list which is empty.
//If called with a list as an argument, only removes gas lists with IDs from that list.
//Must be used after subtracting from a gas. Must be used after assert_gas()
//if assert_gas() was called only to read from the gas.
//By removing empty gases, processing speed is increased.
/datum/gas_mixture/proc/garbage_collect(list/tocheck)
var/list/cached_gases = gases
for(var/id in (tocheck || cached_gases))
if(cached_gases[id][MOLES] <= 0 && cached_gases[id][ARCHIVE] <= 0)
cached_gases -= id
//PV = nRT
/datum/gas_mixture/proc/heat_capacity() //joules per kelvin
var/list/cached_gases = gases
. = 0
for(var/id in cached_gases)
. += cached_gases[id][MOLES] * cached_gases[id][GAS_META][META_GAS_SPECIFIC_HEAT]
/datum/gas_mixture/proc/heat_capacity_archived() //joules per kelvin
var/list/cached_gases = gases
. = 0
for(var/id in cached_gases)
. += cached_gases[id][ARCHIVE] * cached_gases[id][GAS_META][META_GAS_SPECIFIC_HEAT]
/datum/gas_mixture/proc/total_moles() //moles
var/list/cached_gases = gases
. = 0
for(var/id in cached_gases)
. += cached_gases[id][MOLES]
/datum/gas_mixture/proc/return_pressure() //kilopascals
if(volume > 0) // to prevent division by zero
return total_moles() * R_IDEAL_GAS_EQUATION * temperature / volume
return 0
/datum/gas_mixture/proc/return_temperature() //kelvins
return temperature
/datum/gas_mixture/proc/return_volume() //liters
return max(0, volume)
/datum/gas_mixture/proc/thermal_energy() //joules
return temperature * heat_capacity()
//Procedures used for very specific events
/datum/gas_mixture/proc/react(atom/dump_location)
var/list/cached_gases = gases //this speeds things up because >byond
var/reacting = 0 //set to 1 if a notable reaction occured (used by pipe_network)
if(temperature < TCMB)
temperature = TCMB
if(cached_gases["agent_b"] && temperature > 900 && cached_gases["plasma"] && cached_gases["co2"])
//agent b converts hot co2 to o2 (endothermic)
if(cached_gases["plasma"][MOLES] > MINIMUM_HEAT_CAPACITY && cached_gases["co2"][MOLES] > MINIMUM_HEAT_CAPACITY)
var/reaction_rate = min(cached_gases["co2"][MOLES]*0.75, cached_gases["plasma"][MOLES]*0.25, cached_gases["agent_b"][MOLES]*0.05)
cached_gases["co2"][MOLES] -= reaction_rate
assert_gas("o2") //only need to assert oxygen, as this reaction doesn't occur without the other gases existing
cached_gases["o2"][MOLES] += reaction_rate
cached_gases["agent_b"][MOLES] -= reaction_rate*0.05
temperature -= (reaction_rate*20000)/heat_capacity()
garbage_collect()
reacting = 1
/*
if(thermal_energy() > (PLASMA_BINDING_ENERGY*10))
if(cached_gases["plasma"] && cached_gases["co2"] && cached_gases["plasma"][MOLES] > MINIMUM_HEAT_CAPACITY && cached_gases["co2"][MOLES] > MINIMUM_HEAT_CAPACITY && (cached_gases["plasma"][MOLES]+cached_gases["co2"][MOLES])/total_moles() >= FUSION_PURITY_THRESHOLD)//Fusion wont occur if the level of impurities is too high.
//fusion converts plasma and co2 to o2 and n2 (exothermic)
//world << "pre [temperature, [cached_gases["plasma"][MOLES]], [cached_gases["co2"][MOLES]]
var/old_heat_capacity = heat_capacity()
var/carbon_efficency = min(cached_gases["plasma"][MOLES]/cached_gases["co2"][MOLES],MAX_CARBON_EFFICENCY)
var/reaction_energy = thermal_energy()
var/moles_impurities = total_moles()-(cached_gases["plasma"][MOLES]+cached_gases["co2"][MOLES])
var/plasma_fused = (PLASMA_FUSED_COEFFICENT*carbon_efficency)*(temperature/PLASMA_BINDING_ENERGY)
var/carbon_catalyzed = (CARBON_CATALYST_COEFFICENT*carbon_efficency)*(temperature/PLASMA_BINDING_ENERGY)
var/oxygen_added = carbon_catalyzed
var/nitrogen_added = (plasma_fused-oxygen_added)-(thermal_energy()/PLASMA_BINDING_ENERGY)
reaction_energy = max(reaction_energy+((carbon_efficency*cached_gases["plasma"][MOLES])/((moles_impurities/carbon_efficency)+2)*10)+((plasma_fused/(moles_impurities/carbon_efficency))*PLASMA_BINDING_ENERGY),0)
assert_gases("o2", "n2")
cached_gases["plasma"][MOLES] -= plasma_fused
cached_gases["co2"][MOLES] -= carbon_catalyzed
cached_gases["o2"][MOLES] += oxygen_added
cached_gases["n2"][MOLES] += nitrogen_added
garbage_collect()
if(reaction_energy > 0)
reacting = 1
var/new_heat_capacity = heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
temperature = max(((temperature*old_heat_capacity + reaction_energy)/new_heat_capacity),TCMB)
//Prevents whatever mechanism is causing it to hit negative temperatures.
//world << "post [temperature], [cached_gases["plasma"][MOLES]], [cached_gases["co2"][MOLES]]
*/
fuel_burnt = 0
if(temperature > FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
//world << "pre [temperature], [cached_gases["o2"][MOLES]], [cached_gases["plasma"][MOLES]]"
if(fire())
reacting = 1
//world << "post [temperature], [cached_gases["o2"][MOLES]], [cached_gases["plasma"][MOLES]]"
return reacting
/datum/gas_mixture/proc/fire()
//combustion of plasma and volatile fuel, which both act as hydrocarbons (exothermic)
var/energy_released = 0
var/old_heat_capacity = heat_capacity()
var/list/cached_gases = gases //this speeds things up because accessing datum vars is slow
//General volatile gas burn
if(cached_gases["v_fuel"] && cached_gases["v_fuel"][MOLES])
var/burned_fuel
if(!cached_gases["o2"])
burned_fuel = 0
else if(cached_gases["o2"][MOLES] < cached_gases["v_fuel"][MOLES])
burned_fuel = cached_gases["o2"][MOLES]
cached_gases["v_fuel"][MOLES] -= burned_fuel
cached_gases["o2"][MOLES] = 0
else
burned_fuel = cached_gases["v_fuel"][MOLES]
cached_gases["o2"][MOLES] -= cached_gases["v_fuel"][MOLES]
if(burned_fuel)
energy_released += FIRE_CARBON_ENERGY_RELEASED * burned_fuel
assert_gas("co2")
cached_gases["co2"][MOLES] += burned_fuel
fuel_burnt += burned_fuel
//Handle plasma burning
if(cached_gases["plasma"] && cached_gases["plasma"][MOLES] > MINIMUM_HEAT_CAPACITY)
var/plasma_burn_rate = 0
var/oxygen_burn_rate = 0
//more plasma released at higher temperatures
var/temperature_scale
if(temperature > PLASMA_UPPER_TEMPERATURE)
temperature_scale = 1
else
temperature_scale = (temperature-PLASMA_MINIMUM_BURN_TEMPERATURE)/(PLASMA_UPPER_TEMPERATURE-PLASMA_MINIMUM_BURN_TEMPERATURE)
if(temperature_scale > 0)
assert_gas("o2")
oxygen_burn_rate = OXYGEN_BURN_RATE_BASE - temperature_scale
if(cached_gases["o2"][MOLES] > cached_gases["plasma"][MOLES]*PLASMA_OXYGEN_FULLBURN)
plasma_burn_rate = (cached_gases["plasma"][MOLES]*temperature_scale)/PLASMA_BURN_RATE_DELTA
else
plasma_burn_rate = (temperature_scale*(cached_gases["o2"][MOLES]/PLASMA_OXYGEN_FULLBURN))/PLASMA_BURN_RATE_DELTA
if(plasma_burn_rate > MINIMUM_HEAT_CAPACITY)
assert_gas("co2")
cached_gases["plasma"][MOLES] = QUANTIZE(cached_gases["plasma"][MOLES] - plasma_burn_rate)
cached_gases["o2"][MOLES] = QUANTIZE(cached_gases["o2"][MOLES] - (plasma_burn_rate * oxygen_burn_rate))
cached_gases["co2"][MOLES] += plasma_burn_rate
energy_released += FIRE_PLASMA_ENERGY_RELEASED * (plasma_burn_rate)
fuel_burnt += (plasma_burn_rate)*(1+oxygen_burn_rate)
garbage_collect()
if(energy_released > 0)
var/new_heat_capacity = heat_capacity()
if(new_heat_capacity > MINIMUM_HEAT_CAPACITY)
temperature = (temperature*old_heat_capacity + energy_released)/new_heat_capacity
return fuel_burnt
/datum/gas_mixture/proc/archive()
//Update archived versions of variables
//Returns: 1 in all cases
/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
//Merges all air from giver into self. Deletes giver.
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/remove(amount)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
/datum/gas_mixture/proc/remove_ratio(ratio)
//Proportionally removes amount of gas from the gas_mixture
//Returns: gas_mixture with the gases removed
/datum/gas_mixture/proc/copy()
//Creates new, identical gas mixture
//Returns: duplicate gas mixture
/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample)
//Copies variables from sample
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/copy_from_turf(turf/model)
//Copies all gas info from the turf into the gas list along with temperature
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/parse_gas_string(gas_string)
//Copies variables from a particularly formatted string.
//Returns: 1 if we are mutable, 0 otherwise
/datum/gas_mixture/proc/share(datum/gas_mixture/sharer)
//Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length
//Returns: amount of gas exchanged (+ if sharer received)
/datum/gas_mixture/proc/after_share(datum/gas_mixture/sharer)
//called on share's sharer to let it know it just got some gases
/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient)
//Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length
//Returns: new temperature of the sharer
/datum/gas_mixture/proc/compare(datum/gas_mixture/sample)
//Compares sample to self to see if within acceptable ranges that group processing may be enabled
//Returns: a string indicating what check failed, or "" if check passes
/datum/gas_mixture/archive()
var/list/cached_gases = gases
temperature_archived = temperature
for(var/id in cached_gases)
cached_gases[id][ARCHIVE] = cached_gases[id][MOLES]
return 1
/datum/gas_mixture/merge(datum/gas_mixture/giver)
if(!giver)
return 0
//heat transfer
if(abs(temperature - giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/self_heat_capacity = heat_capacity()
var/giver_heat_capacity = giver.heat_capacity()
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
if(combined_heat_capacity)
temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
var/list/giver_gases = giver.gases
//gas transfer
for(var/giver_id in giver_gases)
assert_gas(giver_id)
cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES]
return 1
/datum/gas_mixture/remove(amount)
var/sum = total_moles()
amount = min(amount, sum) //Can not take more air than tile has!
if(amount <= 0)
return null
var/list/cached_gases = gases
var/datum/gas_mixture/removed = new
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
for(var/id in cached_gases)
removed.add_gas(id)
removed_gases[id][MOLES] = QUANTIZE((cached_gases[id][MOLES] / sum) * amount)
cached_gases[id][MOLES] -= removed_gases[id][MOLES]
garbage_collect()
return removed
/datum/gas_mixture/remove_ratio(ratio)
if(ratio <= 0)
return null
ratio = min(ratio, 1)
var/list/cached_gases = gases
var/datum/gas_mixture/removed = new
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
removed.temperature = temperature
for(var/id in cached_gases)
removed.add_gas(id)
removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio)
cached_gases[id][MOLES] -= removed_gases[id][MOLES]
garbage_collect()
return removed
/datum/gas_mixture/copy()
var/list/cached_gases = gases
var/datum/gas_mixture/copy = new
var/list/copy_gases = copy.gases
copy.temperature = temperature
for(var/id in cached_gases)
add_gas(id)
copy_gases[id][MOLES] = cached_gases[id][MOLES]
return copy
/datum/gas_mixture/copy_from(datum/gas_mixture/sample)
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
var/list/sample_gases = sample.gases
temperature = sample.temperature
for(var/id in sample_gases)
assert_gas(id)
cached_gases[id][MOLES] = sample_gases[id][MOLES]
//remove all gases not in the sample
cached_gases &= sample_gases
return 1
/datum/gas_mixture/copy_from_turf(turf/model)
parse_gas_string(model.initial_gas_mix)
//acounts for changes in temperature
var/turf/model_parent = model.parent_type
if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature))
temperature = model.temperature
return 1
/datum/gas_mixture/parse_gas_string(gas_string)
var/list/gases = src.gases
var/list/gas = params2list(gas_string)
if(gas["TEMP"])
temperature = text2num(gas["TEMP"])
gas -= "TEMP"
gases.Cut()
for(var/id in gas)
add_gas(id)
gases[id][MOLES] = text2num(gas[id])
return 1
/datum/gas_mixture/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
if(!sharer)
return 0
var/list/cached_gases = gases
var/list/sharer_gases = sharer.gases
var/temperature_delta = temperature_archived - sharer.temperature_archived
var/abs_temperature_delta = abs(temperature_delta)
var/old_self_heat_capacity = 0
var/old_sharer_heat_capacity = 0
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
old_self_heat_capacity = heat_capacity()
old_sharer_heat_capacity = sharer.heat_capacity()
var/heat_capacity_self_to_sharer = 0 //heat capacity of the moles transferred from us to the sharer
var/heat_capacity_sharer_to_self = 0 //heat capacity of the moles transferred from the sharer to us
var/moved_moles = 0
var/abs_moved_moles = 0
//GAS TRANSFER
for(var/id in sharer_gases - cached_gases) // create gases not in our cache
add_gas(id)
for(var/id in cached_gases) // transfer gases
if(!sharer_gases[id]) //checking here prevents an uneeded proc call if the check fails.
sharer.add_gas(id)
var/gas = cached_gases[id]
var/sharergas = sharer_gases[id]
var/delta = QUANTIZE(gas[ARCHIVE] - sharergas[ARCHIVE])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures
if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/gas_heat_capacity = delta * gas[GAS_META][META_GAS_SPECIFIC_HEAT]
if(delta > 0)
heat_capacity_self_to_sharer += gas_heat_capacity
else
heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative. saves a proc call.
gas[MOLES] -= delta
sharergas[MOLES] += delta
moved_moles += delta
abs_moved_moles += abs(delta)
last_share = abs_moved_moles
//THERMAL ENERGY TRANSFER
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer
var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self
//transfer of thermal energy (via changed heat capacity) between self and sharer
if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY)
temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity
if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY)
sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity
//thermal energy of the system (self and sharer) is unchanged
if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY)
if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.10) // <10% change in sharer heat capacity
temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT)
var/list/unique_gases = cached_gases ^ sharer_gases
if(unique_gases.len) //if all gases were present in both mixtures, we know that no gases are 0
garbage_collect(cached_gases - sharer_gases) //any gases the sharer had, we are guaranteed to have. gases that it didn't have we are not.
sharer.garbage_collect(sharer_gases - cached_gases) //the reverse is equally true
sharer.after_share(src, atmos_adjacent_turfs)
if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
var/delta_pressure = temperature_archived*(total_moles() + moved_moles) - sharer.temperature_archived*(sharer.total_moles() - moved_moles)
return delta_pressure * R_IDEAL_GAS_EQUATION / volume
/datum/gas_mixture/after_share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
return
/datum/gas_mixture/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
//transfer of thermal energy (via conduction) between self and sharer
if(sharer)
sharer_temperature = sharer.temperature_archived
var/temperature_delta = temperature_archived - sharer_temperature
if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
var/self_heat_capacity = heat_capacity_archived()
sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity_archived()
if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
var/heat = conduction_coefficient*temperature_delta* \
(self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))
temperature = max(temperature - heat/self_heat_capacity, TCMB)
sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB)
if(sharer)
sharer.temperature = sharer_temperature
return sharer_temperature
//thermal energy of the system (self and sharer) is unchanged
/datum/gas_mixture/compare(datum/gas_mixture/sample, datatype = MOLES, adjacents = 0)
var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars
var/list/cached_gases = gases
for(var/id in cached_gases | sample_gases) // compare gases from either mixture
var/gas_moles = cached_gases[id] ? cached_gases[id][datatype] : 0
var/sample_moles = sample_gases[id] ? sample_gases[id][datatype] : 0
var/delta = abs(gas_moles - sample_moles)/(adjacents+1)
if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \
delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)
return id
if(total_moles() > MINIMUM_MOLES_DELTA_TO_MOVE)
var/temp
var/sample_temp
switch(datatype)
if(MOLES)
temp = temperature
sample_temp = sample.temperature
if(ARCHIVE)
temp = temperature_archived
sample_temp = sample.temperature_archived
var/temperature_delta = abs(temp - sample_temp)
if((temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \
temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND * temp)
return "temp"
return ""
//Takes the amount of the gas you want to PP as an argument
//So I don't have to do some hacky switches/defines/magic strings
//eg:
//Tox_PP = get_partial_pressure(gas_mixture.toxins)
//O2_PP = get_partial_pressure(gas_mixture.oxygen)
//Does handle trace gases!
/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure)
return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME
//inverse
/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure)
return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature)
//Mathematical proofs:
/*
get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
10/20*5 = 2.5
10 = 2.5/5*20
*/
@@ -0,0 +1,84 @@
var/list/hardcoded_gases = list("o2","n2","co2","plasma") //the main four gases, which were at one time hardcoded
/proc/meta_gas_list()
. = new /list
for(var/gas_path in subtypesof(/datum/gas))
var/list/gas_info = new(4)
var/datum/gas/gas = gas_path
gas_info[META_GAS_SPECIFIC_HEAT] = initial(gas.specific_heat)
gas_info[META_GAS_NAME] = initial(gas.name)
gas_info[META_GAS_MOLES_VISIBLE] = initial(gas.moles_visible)
if(initial(gas.moles_visible) != null)
gas_info[META_GAS_OVERLAY] = new /obj/effect/overlay/gas(initial(gas.gas_overlay))
.[initial(gas.id)] = gas_info
/*||||||||||||||/----------\||||||||||||||*\
||||||||||||||||[GAS DATUMS]||||||||||||||||
||||||||||||||||\__________/||||||||||||||||
||||These should never be instantiated. ||||
||||They exist only to make it easier ||||
||||to add a new gas. They are accessed ||||
||||only by meta_gas_list(). ||||
\*||||||||||||||||||||||||||||||||||||||||*/
/datum/gas
var/id = ""
var/specific_heat = 0
var/name = ""
var/gas_overlay = "" //icon_state in icons/effects/tile_effects.dmi
var/moles_visible = null
/datum/gas/oxygen
id = "o2"
specific_heat = 20
name = "Oxygen"
/datum/gas/nitrogen
id = "n2"
specific_heat = 20
name = "Nitrogen"
/datum/gas/carbon_dioxide //what the fuck is this?
id = "co2"
specific_heat = 30
name = "Carbon Dioxide"
/datum/gas/plasma
id = "plasma"
specific_heat = 200
name = "Plasma"
gas_overlay = "plasma"
moles_visible = MOLES_PLASMA_VISIBLE
/datum/gas/nitrous_oxide
id = "n2o"
specific_heat = 40
name = "Nitrous Oxide"
gas_overlay = "nitrous_oxide"
moles_visible = 1
/datum/gas/oxygen_agent_b
id = "agent_b"
specific_heat = 300
name = "Oxygen Agent B"
/datum/gas/volatile_fuel
id = "v_fuel"
specific_heat = 30
name = "Volatile Fuel"
/datum/gas/bz
id = "bz"
specific_heat = 20
name = "BZ"
/obj/effect/overlay/gas/
icon = 'icons/effects/tile_effects.dmi'
mouse_opacity = 0
layer = FLY_LAYER
appearance_flags = RESET_COLOR|TILE_BOUND
/obj/effect/overlay/gas/New(state)
. = ..()
icon_state = state
@@ -0,0 +1,61 @@
//"immutable" gas mixture used for space calculations
//it can be changed, but any changes will ultimately be undone before they can have any effect
/datum/gas_mixture/space
/datum/gas_mixture/space/New()
..()
temperature = TCMB
temperature_archived = TCMB
/datum/gas_mixture/space/garbage_collect()
gases.Cut() //clever way of ensuring we always are empty.
/datum/gas_mixture/space/archive()
return 1 //nothing changes, so we do nothing and the archive is successful
/datum/gas_mixture/space/merge()
return 0 //we're immutable.
/datum/gas_mixture/space/heat_capacity()
. = 7000
/datum/gas_mixture/space/heat_capacity_archived()
. = heat_capacity()
/datum/gas_mixture/space/remove()
return copy() //we're immutable, so we can just return a copy.
/datum/gas_mixture/space/remove_ratio()
return copy() //we're immutable, so we can just return a copy.
/datum/gas_mixture/space/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
. = ..(sharer, 0)
temperature = TCMB
gases.Cut()
/datum/gas_mixture/space/after_share()
temperature = TCMB
gases.Cut()
/datum/gas_mixture/space/react()
return 0 //we're immutable.
/datum/gas_mixture/space/fire()
return 0 //we're immutable.
/datum/gas_mixture/space/copy()
return new /datum/gas_mixture/space //we're immutable, so we can just return a new instance.
/datum/gas_mixture/space/copy_from()
return 0 //we're immutable.
/datum/gas_mixture/space/copy_from_turf()
return 0 //we're immutable.
/datum/gas_mixture/space/parse_gas_string()
return 0 //we're immutable.
/datum/gas_mixture/space/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
. = ..()
temperature = TCMB
@@ -0,0 +1,702 @@
/datum/tlv
var/min2
var/min1
var/max1
var/max2
/datum/tlv/New(min2 as num, min1 as num, max1 as num, max2 as num)
src.min2 = min2
src.min1 = min1
src.max1 = max1
src.max2 = max2
/datum/tlv/proc/get_danger_level(val as num)
if(max2 != -1 && val >= max2)
return 2
if(min2 != -1 && val <= min2)
return 2
if(max1 != -1 && val >= max1)
return 1
if(min1 != -1 && val <= min1)
return 1
return 0
/obj/item/weapon/electronics/airalarm
name = "air alarm electronics"
icon_state = "airalarm_electronics"
/obj/item/wallframe/airalarm
name = "air alarm frame"
desc = "Used for building Air Alarms"
icon = 'icons/obj/monitors.dmi'
icon_state = "alarm_bitem"
result_path = /obj/machinery/airalarm
#define AALARM_MODE_SCRUBBING 1
#define AALARM_MODE_VENTING 2 //makes draught
#define AALARM_MODE_PANIC 3 //like siphon, but stronger (enables widenet)
#define AALARM_MODE_REPLACEMENT 4 //sucks off all air, then refill and swithes to scrubbing
#define AALARM_MODE_OFF 5
#define AALARM_MODE_FLOOD 6 //Emagged mode; turns off scrubbers and pressure checks on vents
#define AALARM_MODE_SIPHON 7 //Scrubbers suck air
#define AALARM_MODE_CONTAMINATED 8 //Turns on all filtering and widenet scrubbing.
#define AALARM_MODE_REFILL 9 //just like normal, but with triple the air output
#define AALARM_REPORT_TIMEOUT 100
/obj/machinery/airalarm
name = "air alarm"
desc = "A machine that monitors atmosphere levels. Goes off if the area is dangerous."
icon = 'icons/obj/monitors.dmi'
icon_state = "alarm0"
anchored = 1
use_power = 1
idle_power_usage = 4
active_power_usage = 8
power_channel = ENVIRON
req_access = list(access_atmospherics)
var/danger_level = 0
var/mode = AALARM_MODE_SCRUBBING
var/locked = 1
var/aidisabled = 0
var/shorted = 0
var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone
var/frequency = 1439
var/alarm_frequency = 1437
var/datum/radio_frequency/radio_connection
var/list/TLV = list( // Breathable air.
"pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.80, ONE_ATMOSPHERE* 0.90, ONE_ATMOSPHERE * 1.10, ONE_ATMOSPHERE * 1.20), // kPa
"temperature" = new/datum/tlv(T0C, T0C+10, T0C+40, T0C+66), // K
"o2" = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
"n2" = new/datum/tlv(-1, -1, 1000, 1000), // Partial pressure, kpa
"co2" = new/datum/tlv(-1, -1, 5, 10), // Partial pressure, kpa
"plasma" = new/datum/tlv(-1, -1, 0.2, 0.5), // Partial pressure, kpa
"n2o" = new/datum/tlv(-1, -1, 0.2, 0.5), // Partial pressure, kpa
"bz" = new/datum/tlv(-1, -1, 0.2, 0.5)
)
/obj/machinery/airalarm/server // No checks here.
TLV = list(
"pressure" = new/datum/tlv(-1, -1, -1, -1),
"temperature" = new/datum/tlv(-1, -1, -1, -1),
"o2" = new/datum/tlv(-1, -1, -1, -1),
"n2" = new/datum/tlv(-1, -1, -1, -1),
"co2" = new/datum/tlv(-1, -1, -1, -1),
"plasma" = new/datum/tlv(-1, -1, -1, -1),
"n2o" = new/datum/tlv(-1, -1, -1, -1),
"bz" = new/datum/tlv(-1, -1, -1, -1),
)
/obj/machinery/airalarm/kitchen_cold_room // Copypasta: to check temperatures.
TLV = list(
"pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.80, ONE_ATMOSPHERE* 0.90, ONE_ATMOSPHERE * 1.10, ONE_ATMOSPHERE * 1.20), // kPa
"temperature" = new/datum/tlv(200,210,273.15,283.15), // K
"o2" = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa
"n2" = new/datum/tlv(-1, -1, 1000, 1000), // Partial pressure, kpa
"co2" = new/datum/tlv(-1, -1, 5, 10), // Partial pressure, kpa
"plasma" = new/datum/tlv(-1, -1, 0.2, 0.5), // Partial pressure, kpa
"n2o" = new/datum/tlv(-1, -1, 0.2, 0.5), // Partial pressure, kpa
"bz" = new/datum/tlv(-1, -1, 0.2, 0.5), // Partial pressure, kpa
)
//all air alarms in area are connected via magic
/area
var/list/air_vent_names = list()
var/list/air_scrub_names = list()
var/list/air_vent_info = list()
var/list/air_scrub_info = list()
/obj/machinery/airalarm/New(loc, ndir, nbuild)
..()
wires = new /datum/wires/airalarm(src)
if(ndir)
setDir(ndir)
if(nbuild)
buildstage = 0
panel_open = 1
pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24)
pixel_y = (dir & 3)? (dir == 1 ? -24 : 24) : 0
var/area/A = get_area_master(src)
if(name == initial(name))
name = "[A.name] Air Alarm"
update_icon()
if(ticker && ticker.current_state == 3)//if the game is running
initialize()
/obj/machinery/airalarm/Destroy()
if(SSradio)
SSradio.remove_object(src, frequency)
qdel(wires)
wires = null
return ..()
/obj/machinery/airalarm/initialize()
set_frequency(frequency)
/obj/machinery/airalarm/ui_status(mob/user)
if(user.has_unlimited_silicon_privilege && aidisabled)
user << "AI control has been disabled."
else if(!shorted)
return ..()
return UI_CLOSE
/obj/machinery/airalarm/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "airalarm", name, 440, 650, master_ui, state)
ui.open()
/obj/machinery/airalarm/ui_data(mob/user)
var/data = list(
"locked" = locked,
"siliconUser" = user.has_unlimited_silicon_privilege,
"emagged" = emagged,
"danger_level" = danger_level,
)
var/area/A = get_area(src)
data["atmos_alarm"] = A.atmosalm
data["fire_alarm"] = A.fire
var/turf/T = get_turf(src)
var/datum/gas_mixture/environment = T.return_air()
var/datum/tlv/cur_tlv
data["environment_data"] = list()
var/pressure = environment.return_pressure()
cur_tlv = TLV["pressure"]
data["environment_data"] += list(list(
"name" = "Pressure",
"value" = pressure,
"unit" = "kPa",
"danger_level" = cur_tlv.get_danger_level(pressure)
))
var/temperature = environment.temperature
cur_tlv = TLV["temperature"]
data["environment_data"] += list(list(
"name" = "Temperature",
"value" = temperature,
"unit" = "K ([round(temperature - T0C, 0.1)]C)",
"danger_level" = cur_tlv.get_danger_level(temperature)
))
var/total_moles = environment.total_moles()
var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature / environment.volume
for(var/gas_id in environment.gases)
if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
continue
cur_tlv = TLV[gas_id]
data["environment_data"] += list(list(
"name" = environment.gases[gas_id][GAS_META][META_GAS_NAME],
"value" = environment.gases[gas_id][MOLES] / total_moles * 100,
"unit" = "%",
"danger_level" = cur_tlv.get_danger_level(environment.gases[gas_id][MOLES] * partial_pressure)
))
if(!locked || user.has_unlimited_silicon_privilege)
data["vents"] = list()
for(var/id_tag in A.air_vent_names)
var/long_name = A.air_vent_names[id_tag]
var/list/info = A.air_vent_info[id_tag]
if(!info || info["frequency"] != frequency)
continue
data["vents"] += list(list(
"id_tag" = id_tag,
"long_name" = sanitize(long_name),
"power" = info["power"],
"checks" = info["checks"],
"excheck" = info["checks"]&1,
"incheck" = info["checks"]&2,
"direction" = info["direction"],
"external" = info["external"],
"extdefault"= (info["external"] == ONE_ATMOSPHERE)
))
data["scrubbers"] = list()
for(var/id_tag in A.air_scrub_names)
var/long_name = A.air_scrub_names[id_tag]
var/list/info = A.air_scrub_info[id_tag]
if(!info || info["frequency"] != frequency)
continue
data["scrubbers"] += list(list(
"id_tag" = id_tag,
"long_name" = sanitize(long_name),
"power" = info["power"],
"scrubbing" = info["scrubbing"],
"widenet" = info["widenet"],
"filter_co2" = info["filter_co2"],
"filter_toxins" = info["filter_toxins"],
"filter_n2o" = info["filter_n2o"],
"filter_bz" = info["filter_bz"]
))
data["mode"] = mode
data["modes"] = list()
data["modes"] += list(list("name" = "Filtering - Scrubs out contaminants", "mode" = AALARM_MODE_SCRUBBING, "selected" = mode == AALARM_MODE_SCRUBBING, "danger" = 0))
data["modes"] += list(list("name" = "Contaminated - Scrubs out ALL contaminants quickly","mode" = AALARM_MODE_CONTAMINATED, "selected" = mode == AALARM_MODE_CONTAMINATED, "danger" = 0))
data["modes"] += list(list("name" = "Draught - Siphons out air while replacing", "mode" = AALARM_MODE_VENTING, "selected" = mode == AALARM_MODE_VENTING, "danger" = 0))
data["modes"] += list(list("name" = "Refill - Triple vent output", "mode" = AALARM_MODE_REFILL, "selected" = mode == AALARM_MODE_REFILL, "danger" = 0))
data["modes"] += list(list("name" = "Cycle - Siphons air before replacing", "mode" = AALARM_MODE_REPLACEMENT, "selected" = mode == AALARM_MODE_REPLACEMENT, "danger" = 1))
data["modes"] += list(list("name" = "Siphon - Siphons air out of the room", "mode" = AALARM_MODE_SIPHON, "selected" = mode == AALARM_MODE_SIPHON, "danger" = 1))
data["modes"] += list(list("name" = "Panic Siphon - Siphons air out of the room quickly","mode" = AALARM_MODE_PANIC, "selected" = mode == AALARM_MODE_PANIC, "danger" = 1))
data["modes"] += list(list("name" = "Off - Shuts off vents and scrubbers", "mode" = AALARM_MODE_OFF, "selected" = mode == AALARM_MODE_OFF, "danger" = 0))
if(emagged)
data["modes"] += list(list("name" = "Flood - Shuts off scrubbers and opens vents", "mode" = AALARM_MODE_FLOOD, "selected" = mode == AALARM_MODE_FLOOD, "danger" = 1))
var/datum/tlv/selected
var/list/thresholds = list()
selected = TLV["pressure"]
thresholds += list(list("name" = "Pressure", "settings" = list()))
thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min2", "selected" = selected.min2))
thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "min1", "selected" = selected.min1))
thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max1", "selected" = selected.max1))
thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = "max2", "selected" = selected.max2))
selected = TLV["temperature"]
thresholds += list(list("name" = "Temperature", "settings" = list()))
thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min2", "selected" = selected.min2))
thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "min1", "selected" = selected.min1))
thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max1", "selected" = selected.max1))
thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = "max2", "selected" = selected.max2))
for(var/gas_id in meta_gas_info)
if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
continue
selected = TLV[gas_id]
thresholds += list(list("name" = meta_gas_info[gas_id][META_GAS_NAME], "settings" = list()))
thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min2", "selected" = selected.min2))
thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "min1", "selected" = selected.min1))
thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "max1", "selected" = selected.max1))
thresholds[thresholds.len]["settings"] += list(list("env" = gas_id, "val" = "max2", "selected" = selected.max2))
data["thresholds"] = thresholds
return data
/obj/machinery/airalarm/ui_act(action, params)
if(..() || buildstage != 2)
return
if((locked && !usr.has_unlimited_silicon_privilege) || (usr.has_unlimited_silicon_privilege && aidisabled))
return
var/device_id = params["id_tag"]
switch(action)
if("lock")
if(usr.has_unlimited_silicon_privilege && !wires.is_cut(WIRE_IDSCAN))
locked = !locked
. = TRUE
if("power", "co2_scrub", "tox_scrub", "n2o_scrub", "bz_scrub", "widenet", "scrubbing")
send_signal(device_id, list("[action]" = text2num(params["val"])))
. = TRUE
if("excheck")
send_signal(device_id, list("checks" = text2num(params["val"])^1))
. = TRUE
if("incheck")
send_signal(device_id, list("checks" = text2num(params["val"])^2))
. = TRUE
if("set_external_pressure")
var/area/A = get_area_master(src)
var/target = input("New target pressure:", name, A.air_vent_info[device_id]["external"]) as num|null
if(!isnull(target) && !..())
send_signal(device_id, list("set_external_pressure" = target))
. = TRUE
if("reset_external_pressure")
send_signal(device_id, list("reset_external_pressure"))
. = TRUE
if("threshold")
var/env = params["env"]
var/name = params["var"]
var/datum/tlv/tlv = TLV[env]
if(isnull(tlv))
return
var/value = input("New [name] for [env]:", name, tlv.vars[name]) as num|null
if(!isnull(value) && !..())
if(value < 0)
tlv.vars[name] = -1
else
tlv.vars[name] = round(value, 0.01)
. = TRUE
if("mode")
mode = text2num(params["mode"])
apply_mode()
. = TRUE
if("alarm")
var/area/A = get_area_master(src)
if(A.atmosalert(2, src))
post_alert(2)
. = TRUE
if("reset")
var/area/A = get_area_master(src)
if(A.atmosalert(0, src))
post_alert(0)
. = TRUE
update_icon()
/obj/machinery/airalarm/proc/reset(wire)
switch(wire)
if(WIRE_POWER)
if(!wires.is_cut(WIRE_POWER))
shorted = FALSE
update_icon()
if(WIRE_AI)
if(!wires.is_cut(WIRE_AI))
aidisabled = FALSE
/obj/machinery/airalarm/proc/shock(mob/user, prb)
if((stat & (NOPOWER))) // unpowered, no shock
return 0
if(!prob(prb))
return 0 //you lucked out, no shock for you
var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread
s.set_up(5, 1, src)
s.start() //sparks always.
if (electrocute_mob(user, get_area(src), src))
return 1
else
return 0
/obj/machinery/airalarm/proc/refresh_all()
var/area/A = get_area_master(src)
for(var/id_tag in A.air_vent_names)
var/list/I = A.air_vent_info[id_tag]
if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
continue
send_signal(id_tag, list("status"))
for(var/id_tag in A.air_scrub_names)
var/list/I = A.air_scrub_info[id_tag]
if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
continue
send_signal(id_tag, list("status"))
/obj/machinery/airalarm/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency, RADIO_TO_AIRALARM)
/obj/machinery/airalarm/proc/send_signal(target, list/command)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = command
signal.data["tag"] = target
signal.data["sigtype"] = "command"
radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM)
// world << text("Signal [] Broadcasted to []", command, target)
return 1
/obj/machinery/airalarm/proc/apply_mode()
var/area/A = get_area_master(src)
switch(mode)
if(AALARM_MODE_SCRUBBING)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
"co2_scrub" = 1,
"tox_scrub" = 0,
"n2o_scrub" = 0,
"bz_scrub" = 0,
"scrubbing" = 1,
"widenet" = 0,
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 1,
"checks" = 1,
"set_external_pressure" = ONE_ATMOSPHERE
))
if(AALARM_MODE_CONTAMINATED)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
"co2_scrub" = 1,
"tox_scrub" = 1,
"n2o_scrub" = 1,
"bz_scrub" = 1,
"scrubbing" = 1,
"widenet" = 1,
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 1,
"checks" = 1,
"set_external_pressure" = ONE_ATMOSPHERE
))
if(AALARM_MODE_VENTING)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
"widenet" = 0,
"scrubbing" = 0
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 1,
"checks" = 1,
"set_external_pressure" = ONE_ATMOSPHERE*2
))
if(AALARM_MODE_REFILL)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
"co2_scrub" = 1,
"tox_scrub" = 0,
"n2o_scrub" = 0,
"bz_scrub" = 0,
"scrubbing" = 1,
"widenet" = 0,
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 1,
"checks" = 1,
"set_external_pressure" = ONE_ATMOSPHERE * 3
))
if(AALARM_MODE_PANIC,
AALARM_MODE_REPLACEMENT)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
"widenet" = 1,
"scrubbing" = 0
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 0
))
if(AALARM_MODE_SIPHON)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 1,
"widenet" = 0,
"scrubbing" = 0
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 0
))
if(AALARM_MODE_OFF)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 0
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 0
))
if(AALARM_MODE_FLOOD)
for(var/device_id in A.air_scrub_names)
send_signal(device_id, list(
"power" = 0
))
for(var/device_id in A.air_vent_names)
send_signal(device_id, list(
"power" = 1,
"checks" = 2,
"set_internal_pressure" = 0
))
/obj/machinery/airalarm/update_icon()
if(panel_open)
switch(buildstage)
if(2)
icon_state = "alarmx"
if(1)
icon_state = "alarm_b2"
if(0)
icon_state = "alarm_b1"
return
if((stat & (NOPOWER|BROKEN)) || shorted)
icon_state = "alarmp"
return
var/area/A = get_area_master(src)
switch(max(danger_level, A.atmosalm))
if(0)
icon_state = "alarm0"
if(1)
icon_state = "alarm2" //yes, alarm2 is yellow alarm
if(2)
icon_state = "alarm1"
/obj/machinery/airalarm/process()
if((stat & (NOPOWER|BROKEN)) || shorted)
return
var/turf/location = get_turf(src)
if(!location)
return
var/datum/tlv/cur_tlv
var/datum/gas_mixture/environment = location.return_air()
var/list/env_gases = environment.gases
var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature / environment.volume
cur_tlv = TLV["pressure"]
var/environment_pressure = environment.return_pressure()
var/pressure_dangerlevel = cur_tlv.get_danger_level(environment_pressure)
cur_tlv = TLV["temperature"]
var/temperature_dangerlevel = cur_tlv.get_danger_level(environment.temperature)
var/gas_dangerlevel = 0
for(var/gas_id in env_gases)
if(!(gas_id in TLV)) // We're not interested in this gas, it seems.
continue
cur_tlv = TLV[gas_id]
gas_dangerlevel = max(gas_dangerlevel, cur_tlv.get_danger_level(env_gases[gas_id][MOLES] * partial_pressure))
environment.garbage_collect()
var/old_danger_level = danger_level
danger_level = max(pressure_dangerlevel, temperature_dangerlevel, gas_dangerlevel)
if(old_danger_level != danger_level)
apply_danger_level()
if(mode == AALARM_MODE_REPLACEMENT && environment_pressure < ONE_ATMOSPHERE * 0.05)
mode = AALARM_MODE_SCRUBBING
apply_mode()
/obj/machinery/airalarm/proc/post_alert(alert_level)
var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency)
if(!frequency)
return
var/area/A = get_area_master(src)
var/datum/signal/alert_signal = new
alert_signal.source = src
alert_signal.transmission_method = 1
alert_signal.data["zone"] = A.name
alert_signal.data["type"] = "Atmospheric"
if(alert_level==2)
alert_signal.data["alert"] = "severe"
else if (alert_level==1)
alert_signal.data["alert"] = "minor"
else if (alert_level==0)
alert_signal.data["alert"] = "clear"
frequency.post_signal(src, alert_signal,null,-1)
/obj/machinery/airalarm/proc/apply_danger_level()
var/area/A = get_area_master(src)
var/new_area_danger_level = 0
for(var/area/R in A.related)
for(var/obj/machinery/airalarm/AA in R)
if (!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted)
new_area_danger_level = max(new_area_danger_level,AA.danger_level)
if(A.atmosalert(new_area_danger_level,src)) //if area was in normal state or if area was in alert state
post_alert(new_area_danger_level)
update_icon()
/obj/machinery/airalarm/attackby(obj/item/W, mob/user, params)
switch(buildstage)
if(2)
if(istype(W, /obj/item/weapon/wirecutters) && panel_open && wires.is_all_cut())
playsound(src.loc, 'sound/items/Wirecutter.ogg', 50, 1)
user << "<span class='notice'>You cut the final wires.</span>"
var/obj/item/stack/cable_coil/cable = new /obj/item/stack/cable_coil(loc)
cable.amount = 5
buildstage = 1
update_icon()
return
else if(istype(W, /obj/item/weapon/screwdriver)) // Opening that Air Alarm up.
playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
panel_open = !panel_open
user << "<span class='notice'>The wires have been [panel_open ? "exposed" : "unexposed"].</span>"
update_icon()
return
else if(istype(W, /obj/item/weapon/card/id) || istype(W, /obj/item/device/pda))// trying to unlock the interface with an ID card
if(stat & (NOPOWER|BROKEN))
user << "<span class='warning'>It does nothing!</span>"
else
if(src.allowed(usr) && !wires.is_cut(WIRE_IDSCAN))
locked = !locked
user << "<span class='notice'>You [ locked ? "lock" : "unlock"] the air alarm interface.</span>"
else
user << "<span class='danger'>Access denied.</span>"
return
else if(panel_open && is_wire_tool(W))
wires.interact(user)
return
if(1)
if(istype(W, /obj/item/weapon/crowbar))
user.visible_message("[user.name] removes the electronics from [src.name].",\
"<span class='notice'>You start prying out the circuit...</span>")
playsound(src.loc, 'sound/items/Crowbar.ogg', 50, 1)
if (do_after(user, 20/W.toolspeed, target = src))
if (buildstage == 1)
user <<"<span class='notice'>You remove the air alarm electronics.</span>"
new /obj/item/weapon/electronics/airalarm( src.loc )
playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1)
buildstage = 0
update_icon()
return
if(istype(W, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/cable = W
if(cable.get_amount() < 5)
user << "<span class='warning'>You need five lengths of cable to wire the fire alarm!</span>"
return
user.visible_message("[user.name] wires the air alarm.", \
"<span class='notice'>You start wiring the air alarm...</span>")
if (do_after(user, 20, target = src))
if (cable.get_amount() >= 5 && buildstage == 1)
cable.use(5)
user << "<span class='notice'>You wire the air alarm.</span>"
wires.repair()
aidisabled = 0
locked = 1
mode = 1
shorted = 0
post_alert(0)
buildstage = 2
update_icon()
return
if(0)
if(istype(W, /obj/item/weapon/electronics/airalarm))
if(user.unEquip(W))
user << "<span class='notice'>You insert the circuit.</span>"
buildstage = 1
update_icon()
qdel(W)
return
if(istype(W, /obj/item/weapon/wrench))
user << "<span class='notice'>You detach \the [src] from the wall.</span>"
playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
new /obj/item/wallframe/airalarm( user.loc )
qdel(src)
return
return ..()
/obj/machinery/airalarm/power_change()
..()
update_icon()
/obj/machinery/airalarm/emag_act(mob/user)
if(emagged)
return
emagged = TRUE
visible_message("<span class='warning'>Sparks fly out of the [src]!</span>", "<span class='notice'>You emag the [src], disabling its safeties.</span>")
playsound(src.loc, 'sound/effects/sparks4.ogg', 50, 1)
@@ -0,0 +1,290 @@
/*
Quick overview:
Pipes combine to form pipelines
Pipelines and other atmospheric objects combine to form pipe_networks
Note: A single pipe_network represents a completely open space
Pipes -> Pipelines
Pipelines + Other Objects -> Pipe network
*/
/obj/machinery/atmospherics
anchored = 1
idle_power_usage = 0
active_power_usage = 0
power_channel = ENVIRON
on_blueprints = TRUE
layer = GAS_PIPE_LAYER //under wires
var/nodealert = 0
var/can_unwrench = 0
var/initialize_directions = 0
var/pipe_color
var/obj/item/pipe/stored
var/global/list/iconsetids = list()
var/global/list/pipeimages = list()
var/image/pipe_vision_img = null
var/device_type = 0
var/list/obj/machinery/atmospherics/nodes = list()
/obj/machinery/atmospherics/New(loc, process = TRUE)
nodes.len = device_type
..()
if(process)
SSair.atmos_machinery += src
SetInitDirections()
if(can_unwrench)
stored = new(src, make_from=src)
/obj/machinery/atmospherics/Destroy()
for(DEVICE_TYPE_LOOP)
nullifyNode(I)
SSair.atmos_machinery -= src
if(stored)
qdel(stored)
stored = null
dropContents()
if(pipe_vision_img)
qdel(pipe_vision_img)
return ..()
//return QDEL_HINT_FINDREFERENCE
/obj/machinery/atmospherics/proc/nullifyNode(I)
if(NODE_I)
var/obj/machinery/atmospherics/N = NODE_I
N.disconnect(src)
NODE_I = null
//this is called just after the air controller sets up turfs
/obj/machinery/atmospherics/proc/atmosinit(var/list/node_connects)
if(!node_connects) //for pipes where order of nodes doesn't matter
node_connects = list()
node_connects.len = device_type
for(DEVICE_TYPE_LOOP)
for(var/D in cardinal)
if(D & GetInitDirections())
if(D in node_connects)
continue
node_connects[I] = D
break
for(DEVICE_TYPE_LOOP)
for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[I]))
if(can_be_node(target, I))
NODE_I = target
break
update_icon()
/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target)
if(target.initialize_directions & get_dir(target,src))
return 1
/obj/machinery/atmospherics/proc/pipeline_expansion()
return nodes
/obj/machinery/atmospherics/proc/SetInitDirections()
return
/obj/machinery/atmospherics/proc/GetInitDirections()
return initialize_directions
/obj/machinery/atmospherics/proc/returnPipenet()
return
/obj/machinery/atmospherics/proc/returnPipenetAir()
return
/obj/machinery/atmospherics/proc/setPipenet()
return
/obj/machinery/atmospherics/proc/replacePipenet()
return
/obj/machinery/atmospherics/proc/build_network()
// Called to build a network from this node
return
/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference)
if(istype(reference, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/P = reference
qdel(P.parent)
var/I = nodes.Find(reference)
NODE_I = null
update_icon()
/obj/machinery/atmospherics/update_icon()
return
/obj/machinery/atmospherics/attackby(obj/item/weapon/W, mob/user, params)
if(istype(W, /obj/item/weapon/wrench))
if(can_unwrench(user))
var/turf/T = get_turf(src)
if (level==1 && isturf(T) && T.intact)
user << "<span class='warning'>You must remove the plating first!</span>"
return 1
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
add_fingerprint(user)
var/unsafe_wrenching = FALSE
var/internal_pressure = int_air.return_pressure()-env_air.return_pressure()
playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
user << "<span class='notice'>You begin to unfasten \the [src]...</span>"
if (internal_pressure > 2*ONE_ATMOSPHERE)
user << "<span class='warning'>As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?</span>"
unsafe_wrenching = TRUE //Oh dear oh dear
if (do_after(user, 20/W.toolspeed, target = src) && !qdeleted(src))
user.visible_message( \
"[user] unfastens \the [src].", \
"<span class='notice'>You unfasten \the [src].</span>", \
"<span class='italics'>You hear ratchet.</span>")
investigate_log("was <span class='warning'>REMOVED</span> by [key_name(usr)]", "atmos")
//You unwrenched a pipe full of pressure? Let's splat you into the wall, silly.
if(unsafe_wrenching)
unsafe_pressure_release(user, internal_pressure)
Deconstruct()
else
return ..()
/obj/machinery/atmospherics/proc/can_unwrench(mob/user)
return can_unwrench
// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure.
/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null)
if(!user)
return
if(!pressures)
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
pressures = int_air.return_pressure() - env_air.return_pressure()
var/fuck_you_dir = get_dir(src, user) // Because fuck you...
if(!fuck_you_dir)
fuck_you_dir = pick(cardinal)
var/turf/target = get_edge_target_turf(user, fuck_you_dir)
var/range = pressures/250
var/speed = range/5
user.visible_message("<span class='danger'>[user] is sent flying by pressure!</span>","<span class='userdanger'>The pressure sends you flying!</span>")
user.throw_at(target, range, speed)
/obj/machinery/atmospherics/Deconstruct()
if(can_unwrench)
stored.loc = src.loc
transfer_fingerprints_to(stored)
stored = null
qdel(src)
/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255))
//Add identifiers for the iconset
if(iconsetids[iconset] == null)
iconsetids[iconset] = num2text(iconsetids.len + 1)
//Generate a unique identifier for this image combination
var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]"
var/image/img
if(pipeimages[identifier] == null)
img = image(iconset, icon_state=iconstate, dir=direction)
img.color = col
pipeimages[identifier] = img
else
img = pipeimages[identifier]
return img
/obj/machinery/atmospherics/construction(pipe_type, obj_color)
if(can_unwrench)
color = obj_color
pipe_color = obj_color
stored.setDir(src.dir )//need to define them here, because the obj directions...
stored.pipe_type = pipe_type //... were not set at the time the stored pipe was created
stored.color = obj_color
var/turf/T = loc
level = T.intact ? 2 : 1
atmosinit()
var/list/nodes = pipeline_expansion()
for(var/obj/machinery/atmospherics/A in nodes)
A.atmosinit()
A.addMember(src)
build_network()
/obj/machinery/atmospherics/singularity_pull(S, current_size)
if(current_size >= STAGE_FIVE)
Deconstruct()
//Find a connecting /obj/machinery/atmospherics in specified direction
/obj/machinery/atmospherics/proc/findConnecting(direction)
for(var/obj/machinery/atmospherics/target in get_step(src, direction))
if(target.initialize_directions & get_dir(target,src))
return target
#define VENT_SOUND_DELAY 30
/obj/machinery/atmospherics/relaymove(mob/living/user, direction)
if(!(direction & initialize_directions)) //cant go this way.
return
if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug
return
var/obj/machinery/atmospherics/target_move = findConnecting(direction)
if(target_move)
if(target_move.can_crawl_through())
if(is_type_in_list(target_move, ventcrawl_machinery))
user.forceMove(target_move.loc) //handle entering and so on.
user.visible_message("<span class='notice'>You hear something squeezing through the ducts...</span>","<span class='notice'>You climb out the ventilation system.")
else
var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets()
if(pipenetdiff.len)
user.update_pipe_vision(target_move)
user.loc = target_move
user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement
if(world.time - user.last_played_vent > VENT_SOUND_DELAY)
user.last_played_vent = world.time
playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3)
else
if((direction & initialize_directions) || is_type_in_list(src, ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent
user.forceMove(src.loc)
user.visible_message("<span class='notice'>You hear something squeezing through the ducts...</span>","<span class='notice'>You climb out the ventilation system.")
user.canmove = 0
spawn(1)
user.canmove = 1
/obj/machinery/atmospherics/AltClick(mob/living/L)
if(is_type_in_list(src, ventcrawl_machinery))
L.handle_ventcrawl(src)
return
..()
/obj/machinery/atmospherics/proc/can_crawl_through()
return 1
/obj/machinery/atmospherics/proc/returnPipenets()
return list()
/obj/machinery/atmospherics/update_remote_sight(mob/user)
user.sight |= (SEE_TURFS|BLIND)
//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it.
/obj/machinery/atmospherics/proc/can_see_pipes()
return 1
@@ -0,0 +1,34 @@
/obj/machinery/atmospherics/components/binary
icon = 'icons/obj/atmospherics/components/binary_devices.dmi'
dir = SOUTH
initialize_directions = SOUTH|NORTH
use_power = 1
device_type = BINARY
/obj/machinery/atmospherics/components/binary/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions = NORTH|SOUTH
if(SOUTH)
initialize_directions = NORTH|SOUTH
if(EAST)
initialize_directions = EAST|WEST
if(WEST)
initialize_directions = EAST|WEST
/*
Iconnery
*/
/obj/machinery/atmospherics/components/binary/hide(intact)
update_icon()
..(intact)
/*
Housekeeping and pipe network stuff
*/
/obj/machinery/atmospherics/components/binary/atmosinit()
var/node2_connect = dir
var/node1_connect = turn(dir, 180)
var/list/node_connects = list(node1_connect, node2_connect)
..(node_connects)
@@ -0,0 +1,70 @@
//node2, air2, network2 correspond to input
//node1, air1, network1 correspond to output
/obj/machinery/atmospherics/components/binary/circulator
name = "circulator/heat exchanger"
desc = "A gas circulator pump and heat exchanger."
icon_state = "circ1-off"
var/side = CIRC_LEFT
var/status = 0
var/last_pressure_delta = 0
anchored = 1
density = 1
var/global/const/CIRC_LEFT = 1
var/global/const/CIRC_RIGHT = 2
/obj/machinery/atmospherics/components/binary/circulator/proc/return_transfer_air()
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
var/output_starting_pressure = air1.return_pressure()
var/input_starting_pressure = air2.return_pressure()
if(output_starting_pressure >= input_starting_pressure-10)
//Need at least 10 KPa difference to overcome friction in the mechanism
last_pressure_delta = 0
return null
//Calculate necessary moles to transfer using PV = nRT
if(air2.temperature>0)
var/pressure_delta = (input_starting_pressure - output_starting_pressure)/2
var/transfer_moles = pressure_delta*air1.volume/(air2.temperature * R_IDEAL_GAS_EQUATION)
last_pressure_delta = pressure_delta
//world << "pressure_delta = [pressure_delta]; transfer_moles = [transfer_moles];"
//Actually transfer the gas
var/datum/gas_mixture/removed = air2.remove(transfer_moles)
update_parents()
return removed
else
last_pressure_delta = 0
/obj/machinery/atmospherics/components/binary/circulator/process_atmos()
..()
update_icon()
/obj/machinery/atmospherics/components/binary/circulator/update_icon()
if(stat & (BROKEN|NOPOWER))
icon_state = "circ[side]-p"
else if(last_pressure_delta > 0)
if(last_pressure_delta > ONE_ATMOSPHERE)
icon_state = "circ[side]-run"
else
icon_state = "circ[side]-slow"
else
icon_state = "circ[side]-off"
return 1
@@ -0,0 +1,198 @@
/*
Acts like a normal vent, but has an input AND output.
*/
#define EXT_BOUND 1
#define INPUT_MIN 2
#define OUTPUT_MAX 4
/obj/machinery/atmospherics/components/binary/dp_vent_pump
icon = 'icons/obj/atmospherics/components/unary_devices.dmi' //We reuse the normal vent icons!
icon_state = "dpvent_map"
//node2 is output port
//node1 is input port
name = "dual-port air vent"
desc = "Has a valve and pump attached to it. There are two ports."
level = 1
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
var/on = 0
var/pump_direction = 1 //0 = siphoning, 1 = releasing
var/external_pressure_bound = ONE_ATMOSPHERE
var/input_pressure_min = 0
var/output_pressure_max = 0
var/pressure_checks = EXT_BOUND
//EXT_BOUND: Do not pass external_pressure_bound
//INPUT_MIN: Do not pass input_pressure_min
//OUTPUT_MAX: Do not pass output_pressure_max
/obj/machinery/atmospherics/components/binary/dp_vent_pump/Destroy()
if(SSradio)
SSradio.remove_object(src, frequency)
return ..()
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume
name = "large dual-port air vent"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/high_volume/New()
..()
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
air1.volume = 1000
air2.volume = 1000
/obj/machinery/atmospherics/components/binary/dp_vent_pump/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "dpvent_cap"))
if(!on || stat & (NOPOWER|BROKEN))
icon_state = "vent_off"
return
if(pump_direction)
icon_state = "vent_out"
else
icon_state = "vent_in"
/obj/machinery/atmospherics/components/binary/dp_vent_pump/process_atmos()
..()
if(!on)
return 0
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
var/datum/gas_mixture/environment = loc.return_air()
var/environment_pressure = environment.return_pressure()
if(pump_direction) //input -> external
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure))
if(pressure_checks&INPUT_MIN)
pressure_delta = min(pressure_delta, (air1.return_pressure() - input_pressure_min))
if(pressure_delta > 0)
if(air1.temperature > 0)
var/transfer_moles = pressure_delta*environment.volume/(air1.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = air1.remove(transfer_moles)
loc.assume_air(removed)
air_update_turf()
var/datum/pipeline/parent1 = PARENT1
parent1.update = 1
else //external -> output
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound))
if(pressure_checks&INPUT_MIN)
pressure_delta = min(pressure_delta, (output_pressure_max - air2.return_pressure()))
if(pressure_delta > 0)
if(environment.temperature > 0)
var/transfer_moles = pressure_delta*air2.volume/(environment.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
air2.merge(removed)
air_update_turf()
var/datum/pipeline/parent2 = PARENT2
parent2.update = 1
return 1
//Radio remote control
/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/binary/dp_vent_pump/proc/broadcast_status()
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = list(
"tag" = id,
"device" = "ADVP",
"power" = on,
"direction" = pump_direction?("release"):("siphon"),
"checks" = pressure_checks,
"input" = input_pressure_min,
"output" = output_pressure_max,
"external" = external_pressure_bound,
"sigtype" = "status"
)
radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
return 1
/obj/machinery/atmospherics/components/binary/dp_vent_pump/atmosinit()
..()
if(frequency)
set_frequency(frequency)
broadcast_status()
/obj/machinery/atmospherics/components/binary/dp_vent_pump/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
return 0
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("set_direction" in signal.data)
pump_direction = text2num(signal.data["set_direction"])
if("checks" in signal.data)
pressure_checks = text2num(signal.data["checks"])
if("purge" in signal.data)
pressure_checks &= ~1
pump_direction = 0
if("stabalize" in signal.data)
pressure_checks |= 1
pump_direction = 1
if("set_input_pressure" in signal.data)
input_pressure_min = Clamp(text2num(signal.data["set_input_pressure"]),0,ONE_ATMOSPHERE*50)
if("set_output_pressure" in signal.data)
output_pressure_max = Clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50)
if("set_external_pressure" in signal.data)
external_pressure_bound = Clamp(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50)
if("status" in signal.data)
spawn(2)
broadcast_status()
return //do not update_icon
//if(signal.data["tag"])
spawn(2)
broadcast_status()
update_icon()
#undef EXT_BOUND
#undef INPUT_MIN
#undef OUTPUT_MAX
@@ -0,0 +1,175 @@
/*
Passive gate is similar to the regular pump except:
* It doesn't require power
* Can not transfer low pressure to higher pressure (so it's more like a valve where you can control the flow)
*/
/obj/machinery/atmospherics/components/binary/passive_gate
icon_state = "passgate_map"
name = "passive gate"
desc = "A one-way air valve that does not require power."
can_unwrench = 1
var/on = 0
var/target_pressure = ONE_ATMOSPHERE
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
/obj/machinery/atmospherics/components/binary/passive_gate/Destroy()
if(SSradio)
SSradio.remove_object(src,frequency)
return ..()
/obj/machinery/atmospherics/components/binary/passive_gate/update_icon_nopipes()
if(!on)
icon_state = "passgate_off"
cut_overlays()
return
add_overlay(getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "passgate_on"))
/obj/machinery/atmospherics/components/binary/passive_gate/process_atmos()
..()
if(!on)
return 0
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
var/output_starting_pressure = air2.return_pressure()
var/input_starting_pressure = air1.return_pressure()
if(output_starting_pressure >= min(target_pressure,input_starting_pressure-10))
//No need to pump gas if target is already reached or input pressure is too low
//Need at least 10 KPa difference to overcome friction in the mechanism
return 1
//Calculate necessary moles to transfer using PV = nRT
if((air1.total_moles() > 0) && (air1.temperature>0))
var/pressure_delta = min(target_pressure - output_starting_pressure, (input_starting_pressure - output_starting_pressure)/2)
//Can not have a pressure delta that would cause output_pressure > input_pressure
var/transfer_moles = pressure_delta*air2.volume/(air1.temperature * R_IDEAL_GAS_EQUATION)
//Actually transfer the gas
var/datum/gas_mixture/removed = air1.remove(transfer_moles)
air2.merge(removed)
update_parents()
//Radio remote control
/obj/machinery/atmospherics/components/binary/passive_gate/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/binary/passive_gate/proc/broadcast_status()
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = list(
"tag" = id,
"device" = "AGP",
"power" = on,
"target_output" = target_pressure,
"sigtype" = "status"
)
radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
return 1
/obj/machinery/atmospherics/components/binary/passive_gate/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "atmos_pump", name, 335, 115, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/binary/passive_gate/ui_data()
var/data = list()
data["on"] = on
data["pressure"] = round(target_pressure)
data["max_pressure"] = round(MAX_OUTPUT_PRESSURE)
return data
/obj/machinery/atmospherics/components/binary/passive_gate/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos")
. = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "max")
pressure = MAX_OUTPUT_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New output pressure (0-[MAX_OUTPUT_PRESSURE] kPa):", name, target_pressure) as num|null
if(!isnull(pressure) || !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
target_pressure = Clamp(pressure, 0, MAX_OUTPUT_PRESSURE)
investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", "atmos")
update_icon()
/obj/machinery/atmospherics/components/binary/passive_gate/atmosinit()
..()
if(frequency)
set_frequency(frequency)
/obj/machinery/atmospherics/components/binary/passive_gate/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
return 0
var/old_on = on //for logging
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("set_output_pressure" in signal.data)
target_pressure = Clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50)
if(on != old_on)
investigate_log("was turned [on ? "on" : "off"] by a remote signal", "atmos")
if("status" in signal.data)
broadcast_status()
return
broadcast_status()
update_icon()
return
/obj/machinery/atmospherics/components/binary/passive_gate/power_change()
..()
update_icon()
/obj/machinery/atmospherics/components/binary/passive_gate/can_unwrench(mob/user)
if(..())
if(on)
user << "<span class='warning'>You cannot unwrench this [src], turn it off first!</span>"
else
return 1
@@ -0,0 +1,182 @@
/*
Every cycle, the pump uses the air in air_in to try and make air_out the perfect pressure.
node1, air1, network1 correspond to input
node2, air2, network2 correspond to output
Thus, the two variables affect pump operation are set in New():
air1.volume
This is the volume of gas available to the pump that may be transfered to the output
air2.volume
Higher quantities of this cause more air to be perfected later
but overall network volume is also increased as this increases...
*/
/obj/machinery/atmospherics/components/binary/pump
icon_state = "pump_map"
name = "gas pump"
desc = "A pump that moves gas by pressure."
can_unwrench = 1
var/on = 0
var/target_pressure = ONE_ATMOSPHERE
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
/obj/machinery/atmospherics/components/binary/pump/on
on = 1
/obj/machinery/atmospherics/components/binary/pump/Destroy()
if(SSradio)
SSradio.remove_object(src,frequency)
if(radio_connection)
radio_connection = null
return ..()
/obj/machinery/atmospherics/components/binary/pump/update_icon_nopipes()
if(stat & NOPOWER)
icon_state = "pump_off"
return
icon_state = "pump_[on?"on":"off"]"
/obj/machinery/atmospherics/components/binary/pump/process_atmos()
// ..()
if(stat & (NOPOWER|BROKEN))
return 0
if(!on)
return 0
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
var/output_starting_pressure = air2.return_pressure()
if((target_pressure - output_starting_pressure) < 0.01)
//No need to pump gas if target is already reached!
return 1
//Calculate necessary moles to transfer using PV=nRT
if((air1.total_moles() > 0) && (air1.temperature>0))
var/pressure_delta = target_pressure - output_starting_pressure
var/transfer_moles = pressure_delta*air2.volume/(air1.temperature * R_IDEAL_GAS_EQUATION)
//Actually transfer the gas
var/datum/gas_mixture/removed = air1.remove(transfer_moles)
air2.merge(removed)
update_parents()
return 1
//Radio remote control
/obj/machinery/atmospherics/components/binary/pump/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency, filter = RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/binary/pump/proc/broadcast_status()
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = list(
"tag" = id,
"device" = "AGP",
"power" = on,
"target_output" = target_pressure,
"sigtype" = "status"
)
radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA)
return 1
/obj/machinery/atmospherics/components/binary/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "atmos_pump", name, 335, 115, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/binary/pump/ui_data()
var/data = list()
data["on"] = on
data["pressure"] = round(target_pressure)
data["max_pressure"] = round(MAX_OUTPUT_PRESSURE)
return data
/obj/machinery/atmospherics/components/binary/pump/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos")
. = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "max")
pressure = MAX_OUTPUT_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New output pressure (0-[MAX_OUTPUT_PRESSURE] kPa):", name, target_pressure) as num|null
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
target_pressure = Clamp(pressure, 0, MAX_OUTPUT_PRESSURE)
investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", "atmos")
update_icon()
/obj/machinery/atmospherics/components/binary/pump/atmosinit()
..()
if(frequency)
set_frequency(frequency)
/obj/machinery/atmospherics/components/binary/pump/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
return 0
var/old_on = on //for logging
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("set_output_pressure" in signal.data)
target_pressure = Clamp(text2num(signal.data["set_output_pressure"]),0,ONE_ATMOSPHERE*50)
if(on != old_on)
investigate_log("was turned [on ? "on" : "off"] by a remote signal", "atmos")
if("status" in signal.data)
broadcast_status()
return
broadcast_status()
update_icon()
return
/obj/machinery/atmospherics/components/binary/pump/power_change()
..()
update_icon()
/obj/machinery/atmospherics/components/binary/pump/can_unwrench(mob/user)
if(..())
if(!(stat & NOPOWER) && on)
user << "<span class='warning'>You cannot unwrench this [src], turn it off first!</span>"
else
return 1
@@ -0,0 +1,72 @@
/*
It's like a regular ol' straight pipe, but you can turn it on and off.
*/
/obj/machinery/atmospherics/components/binary/valve
icon_state = "mvalve_map"
name = "manual valve"
desc = "A pipe valve"
can_unwrench = 1
var/frequency = 0
var/id = null
var/open = 0
var/valve_type = "m" //lets us have a nice, clean, OOP update_icon_nopipes()
/obj/machinery/atmospherics/components/binary/valve/open
open = 1
/obj/machinery/atmospherics/components/binary/valve/update_icon_nopipes(animation = 0)
normalize_dir()
if(animation)
flick("[valve_type]valve_[open][!open]",src)
icon_state = "[valve_type]valve_[open?"on":"off"]"
/obj/machinery/atmospherics/components/binary/valve/proc/open()
open = 1
update_icon_nopipes()
update_parents()
var/datum/pipeline/parent1 = PARENT1
parent1.reconcile_air()
investigate_log("was opened by [usr ? key_name(usr) : "a remote signal"]", "atmos")
/obj/machinery/atmospherics/components/binary/valve/proc/close()
open = 0
update_icon_nopipes()
investigate_log("was closed by [usr ? key_name(usr) : "a remote signal"]", "atmos")
/obj/machinery/atmospherics/components/binary/valve/proc/normalize_dir()
if(dir==SOUTH)
setDir(NORTH)
else if(dir==WEST)
setDir(EAST)
/obj/machinery/atmospherics/components/binary/valve/attack_ai(mob/user)
return
/obj/machinery/atmospherics/components/binary/valve/attack_hand(mob/user)
add_fingerprint(usr)
update_icon_nopipes(1)
sleep(10)
if(open)
close()
return
open()
/obj/machinery/atmospherics/components/binary/valve/digital // can be controlled by AI
name = "digital valve"
desc = "A digitally controlled valve."
icon_state = "dvalve_map"
valve_type = "d"
/obj/machinery/atmospherics/components/binary/valve/digital/attack_ai(mob/user)
return src.attack_hand(user)
/obj/machinery/atmospherics/components/binary/valve/digital/update_icon_nopipes(animation)
if(stat & NOPOWER)
normalize_dir()
icon_state = "dvalve_nopower"
return
..()
@@ -0,0 +1,178 @@
/*
Every cycle, the pump uses the air in air_in to try and make air_out the perfect pressure.
node1, air1, network1 correspond to input
node2, air2, network2 correspond to output
Thus, the two variables affect pump operation are set in New():
air1.volume
This is the volume of gas available to the pump that may be transfered to the output
air2.volume
Higher quantities of this cause more air to be perfected later
but overall network volume is also increased as this increases...
*/
/obj/machinery/atmospherics/components/binary/volume_pump
icon_state = "volpump_map"
name = "volumetric gas pump"
desc = "A pump that moves gas by volume."
can_unwrench = 1
var/on = 0
var/transfer_rate = MAX_TRANSFER_RATE
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
/obj/machinery/atmospherics/components/binary/volume_pump/Destroy()
if(SSradio)
SSradio.remove_object(src,frequency)
return ..()
/obj/machinery/atmospherics/components/binary/volume_pump/on
on = 1
/obj/machinery/atmospherics/components/binary/volume_pump/update_icon_nopipes()
if(stat & NOPOWER)
icon_state = "volpump_off"
return
icon_state = "volpump_[on?"on":"off"]"
/obj/machinery/atmospherics/components/binary/volume_pump/process_atmos()
// ..()
if(stat & (NOPOWER|BROKEN))
return
if(!on)
return 0
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
// Pump mechanism just won't do anything if the pressure is too high/too low
var/input_starting_pressure = air1.return_pressure()
var/output_starting_pressure = air2.return_pressure()
if((input_starting_pressure < 0.01) || (output_starting_pressure > 9000))
return 1
var/transfer_ratio = min(1, transfer_rate/air1.volume)
var/datum/gas_mixture/removed = air1.remove_ratio(transfer_ratio)
air2.merge(removed)
update_parents()
return 1
/obj/machinery/atmospherics/components/binary/volume_pump/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency)
/obj/machinery/atmospherics/components/binary/volume_pump/proc/broadcast_status()
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = list(
"tag" = id,
"device" = "APV",
"power" = on,
"transfer_rate" = transfer_rate,
"sigtype" = "status"
)
radio_connection.post_signal(src, signal)
return 1
/obj/machinery/atmospherics/components/binary/volume_pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "atmos_pump", name, 310, 115, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/binary/volume_pump/ui_data()
var/data = list()
data["on"] = on
data["rate"] = round(transfer_rate)
data["max_rate"] = round(MAX_TRANSFER_RATE)
return data
/obj/machinery/atmospherics/components/binary/volume_pump/atmosinit()
..()
set_frequency(frequency)
/obj/machinery/atmospherics/components/binary/volume_pump/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos")
. = TRUE
if("rate")
var/rate = params["rate"]
if(rate == "max")
rate = MAX_TRANSFER_RATE
. = TRUE
else if(rate == "input")
rate = input("New transfer rate (0-[MAX_TRANSFER_RATE] L/s):", name, transfer_rate) as num|null
if(!isnull(rate) && !..())
. = TRUE
else if(text2num(rate) != null)
rate = text2num(rate)
. = TRUE
if(.)
transfer_rate = Clamp(rate, 0, MAX_TRANSFER_RATE)
investigate_log("was set to [transfer_rate] L/s by [key_name(usr)]", "atmos")
update_icon()
/obj/machinery/atmospherics/components/binary/volume_pump/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
return 0
var/old_on = on //for logging
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("set_transfer_rate" in signal.data)
var/datum/gas_mixture/air1 = AIR1
transfer_rate = Clamp(text2num(signal.data["set_transfer_rate"]),0,air1.volume)
if(on != old_on)
investigate_log("was turned [on ? "on" : "off"] by a remote signal", "atmos")
if("status" in signal.data)
broadcast_status()
return //do not update_icon
broadcast_status()
update_icon()
return
/obj/machinery/atmospherics/components/binary/volume_pump/power_change()
..()
update_icon()
/obj/machinery/atmospherics/components/binary/volume_pump/can_unwrench(mob/user)
if(..())
if(!(stat & NOPOWER) && on)
user << "<span class='warning'>You cannot unwrench this [src], turn it off first!</span>"
else
return 1
@@ -0,0 +1,172 @@
/*
So much of atmospherics.dm was used solely by components, so separating this makes things all a lot cleaner.
On top of that, now people can add component-speciic procs/vars if they want!
*/
/obj/machinery/atmospherics/components
var/welded = 0 //Used on pumps and scrubbers
var/showpipe = 0
var/list/datum/pipeline/parents = list()
var/list/datum/gas_mixture/airs = list()
/obj/machinery/atmospherics/components/New()
parents.len = device_type
airs.len = device_type
..()
for(DEVICE_TYPE_LOOP)
var/datum/gas_mixture/A = new
A.volume = 200
AIR_I = A
/*
Iconnery
*/
/obj/machinery/atmospherics/components/proc/icon_addintact(var/obj/machinery/atmospherics/node)
var/image/img = getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_intact", get_dir(src,node), node.pipe_color)
underlays += img
return img.dir
/obj/machinery/atmospherics/components/proc/icon_addbroken(var/connected = 0)
var/unconnected = (~connected) & initialize_directions
for(var/direction in cardinal)
if(unconnected & direction)
underlays += getpipeimage('icons/obj/atmospherics/components/binary_devices.dmi', "pipe_exposed", direction)
/obj/machinery/atmospherics/components/proc/update_icon_nopipes()
return
/obj/machinery/atmospherics/components/update_icon()
update_icon_nopipes()
underlays.Cut()
var/turf/T = loc
if(level == 2 || !T.intact)
showpipe = 1
else
showpipe = 0
if(!showpipe)
return //no need to update the pipes if they aren't showing
var/connected = 0
for(DEVICE_TYPE_LOOP) //adds intact pieces
if(NODE_I)
connected |= icon_addintact(NODE_I)
icon_addbroken(connected) //adds broken pieces
/*
Pipenet stuff; housekeeping
*/
/obj/machinery/atmospherics/components/nullifyNode(I)
..()
if(NODE_I)
nullifyPipenet(PARENT_I)
qdel(AIR_I)
AIR_I = null
/obj/machinery/atmospherics/components/construction()
..()
update_parents()
/obj/machinery/atmospherics/components/build_network()
for(DEVICE_TYPE_LOOP)
if(!PARENT_I)
PARENT_I = new /datum/pipeline()
var/datum/pipeline/P = PARENT_I
P.build_pipeline(src)
/obj/machinery/atmospherics/components/proc/nullifyPipenet(datum/pipeline/reference)
var/I = parents.Find(reference)
reference.other_airs -= AIR_I
reference.other_atmosmch -= src
PARENT_I = null
/obj/machinery/atmospherics/components/returnPipenetAir(datum/pipeline/reference)
var/I = parents.Find(reference)
return AIR_I
/obj/machinery/atmospherics/components/pipeline_expansion(datum/pipeline/reference)
if(reference)
var/I = parents.Find(reference)
return list(NODE_I)
else
return ..()
/obj/machinery/atmospherics/components/setPipenet(datum/pipeline/reference, obj/machinery/atmospherics/A)
var/I = nodes.Find(A)
PARENT_I = reference
/obj/machinery/atmospherics/components/returnPipenet(obj/machinery/atmospherics/A = NODE1) //returns PARENT1 if called without argument
var/I = nodes.Find(A)
return PARENT_I
/obj/machinery/atmospherics/components/replacePipenet(datum/pipeline/Old, datum/pipeline/New)
var/I = parents.Find(Old)
PARENT_I = New
/obj/machinery/atmospherics/components/unsafe_pressure_release(var/mob/user, var/pressures)
..()
var/turf/T = get_turf(src)
if(T)
//Remove the gas from airs and assume it
var/datum/gas_mixture/environment = T.return_air()
var/lost = null
var/times_lost = 0
for(DEVICE_TYPE_LOOP)
var/datum/gas_mixture/air = AIR_I
lost += pressures*environment.volume/(air.temperature * R_IDEAL_GAS_EQUATION)
times_lost++
var/shared_loss = lost/times_lost
var/datum/gas_mixture/to_release
for(DEVICE_TYPE_LOOP)
var/datum/gas_mixture/air = AIR_I
if(!to_release)
to_release = air.remove(shared_loss)
continue
to_release.merge(air.remove(shared_loss))
T.assume_air(to_release)
air_update_turf(1)
/obj/machinery/atmospherics/components/proc/safe_input(var/title, var/text, var/default_set)
var/new_value = input(usr,text,title,default_set) as num
if(usr.canUseTopic(src))
return new_value
return default_set
/*
Helpers
*/
/obj/machinery/atmospherics/components/proc/update_parents()
for(DEVICE_TYPE_LOOP)
var/datum/pipeline/parent = PARENT_I
if(!parent)
throw EXCEPTION("Component is missing a pipenet! Rebuilding...")
build_network()
parent.update = 1
/obj/machinery/atmospherics/components/returnPipenets()
. = list()
for(DEVICE_TYPE_LOOP)
. += returnPipenet(NODE_I)
/*
UI Stuff
*/
/obj/machinery/atmospherics/components/ui_status(mob/user)
if(allowed(user))
return ..()
user << "<span class='danger'>Access denied.</span>"
return UI_CLOSE
@@ -0,0 +1,155 @@
/obj/machinery/atmospherics/components/trinary/filter
name = "gas filter"
icon_state = "filter_off"
density = 0
can_unwrench = 1
var/on = 0
var/target_pressure = ONE_ATMOSPHERE
var/filter_type = ""
var/frequency = 0
var/datum/radio_frequency/radio_connection
/obj/machinery/atmospherics/components/trinary/filter/flipped
icon_state = "filter_off_f"
flipped = 1
/obj/machinery/atmospherics/components/trinary/filter/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA)
/obj/machinery/atmospherics/components/trinary/filter/Destroy()
if(SSradio)
SSradio.remove_object(src,frequency)
return ..()
/obj/machinery/atmospherics/components/trinary/filter/update_icon()
cut_overlays()
for(var/direction in cardinal)
if(direction & initialize_directions)
var/obj/machinery/atmospherics/node = findConnecting(direction)
if(node)
add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction, node.pipe_color))
continue
add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction))
..()
/obj/machinery/atmospherics/components/trinary/filter/update_icon_nopipes()
if(!(stat & NOPOWER) && on && NODE1 && NODE2 && NODE3)
icon_state = "filter_on[flipped?"_f":""]"
return
icon_state = "filter_off[flipped?"_f":""]"
/obj/machinery/atmospherics/components/trinary/filter/power_change()
var/old_stat = stat
..()
if(stat & NOPOWER)
on = 0
if(old_stat != stat)
update_icon()
/obj/machinery/atmospherics/components/trinary/filter/process_atmos()
..()
if(!on)
return 0
if(!(NODE1 && NODE2 && NODE3))
return 0
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
var/datum/gas_mixture/air3 = AIR3
var/output_starting_pressure = air3.return_pressure()
if(output_starting_pressure >= target_pressure)
//No need to mix if target is already full!
return 1
//Calculate necessary moles to transfer using PV=nRT
var/pressure_delta = target_pressure - output_starting_pressure
var/transfer_moles
if(air1.temperature > 0)
transfer_moles = pressure_delta*air3.volume/(air1.temperature * R_IDEAL_GAS_EQUATION)
//Actually transfer the gas
if(transfer_moles > 0)
var/datum/gas_mixture/removed = air1.remove(transfer_moles)
if(!removed)
return
var/datum/gas_mixture/filtered_out = new
filtered_out.temperature = removed.temperature
if(filter_type && removed.gases[filter_type])
filtered_out.assert_gas(filter_type)
filtered_out.gases[filter_type][MOLES] = removed.gases[filter_type][MOLES]
removed.gases[filter_type][MOLES] = 0
removed.garbage_collect()
else
filtered_out = null
air2.merge(filtered_out)
air3.merge(removed)
update_parents()
return 1
/obj/machinery/atmospherics/components/trinary/filter/atmosinit()
set_frequency(frequency)
return ..()
/obj/machinery/atmospherics/components/trinary/filter/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "atmos_filter", name, 475, 140, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/trinary/filter/ui_data()
var/data = list()
data["on"] = on
data["pressure"] = round(target_pressure)
data["max_pressure"] = round(MAX_OUTPUT_PRESSURE)
data["filter_type"] = filter_type
return data
/obj/machinery/atmospherics/components/trinary/filter/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos")
. = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "max")
pressure = MAX_OUTPUT_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New output pressure (0-[MAX_OUTPUT_PRESSURE] kPa):", name, target_pressure) as num|null
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
target_pressure = Clamp(pressure, 0, MAX_OUTPUT_PRESSURE)
investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", "atmos")
if("filter")
filter_type = ""
var/filter_name = "nothing"
var/gas = params["mode"]
if(gas in meta_gas_info)
filter_type = gas
filter_name = meta_gas_info[gas][META_GAS_NAME]
investigate_log("was set to filter [filter_name] by [key_name(usr)]", "atmos")
. = TRUE
update_icon()
@@ -0,0 +1,170 @@
/obj/machinery/atmospherics/components/trinary/mixer
icon_state = "mixer_off"
density = 0
name = "gas mixer"
can_unwrench = 1
var/on = 0
var/target_pressure = ONE_ATMOSPHERE
var/node1_concentration = 0.5
var/node2_concentration = 0.5
//node 3 is the outlet, nodes 1 & 2 are intakes
/obj/machinery/atmospherics/components/trinary/mixer/flipped
icon_state = "mixer_off_f"
flipped = 1
/obj/machinery/atmospherics/components/trinary/mixer/update_icon()
cut_overlays()
for(var/direction in cardinal)
if(direction & initialize_directions)
var/obj/machinery/atmospherics/node = findConnecting(direction)
if(node)
add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction, node.pipe_color))
continue
add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction))
return ..()
/obj/machinery/atmospherics/components/trinary/mixer/update_icon_nopipes()
if(!(stat & NOPOWER) && on && NODE1 && NODE2 && NODE3)
icon_state = "mixer_on[flipped?"_f":""]"
return
icon_state = "mixer_off[flipped?"_f":""]"
/obj/machinery/atmospherics/components/trinary/mixer/power_change()
var/old_stat = stat
..()
if(stat & NOPOWER)
on = 0
if(old_stat != stat)
update_icon()
/obj/machinery/atmospherics/components/trinary/mixer/New()
..()
var/datum/gas_mixture/air3 = AIR3
air3.volume = 300
AIR3 = air3
/obj/machinery/atmospherics/components/trinary/mixer/process_atmos()
..()
if(!on)
return 0
if(!(NODE1 && NODE2 && NODE3))
return 0
var/datum/gas_mixture/air1 = AIR1
var/datum/gas_mixture/air2 = AIR2
var/datum/gas_mixture/air3 = AIR3
var/output_starting_pressure = air3.return_pressure()
if(output_starting_pressure >= target_pressure)
//No need to mix if target is already full!
return 1
//Calculate necessary moles to transfer using PV=nRT
var/pressure_delta = target_pressure - output_starting_pressure
var/transfer_moles1 = 0
var/transfer_moles2 = 0
if(air1.temperature > 0)
transfer_moles1 = (node1_concentration * pressure_delta) * air3.volume / (air1.temperature * R_IDEAL_GAS_EQUATION)
if(air2.temperature > 0)
transfer_moles2 = (node2_concentration * pressure_delta) * air3.volume / (air2.temperature * R_IDEAL_GAS_EQUATION)
var/air1_moles = air1.total_moles()
var/air2_moles = air2.total_moles()
if((air1_moles < transfer_moles1) || (air2_moles < transfer_moles2))
var/ratio = 0
if((transfer_moles1 > 0 ) && (transfer_moles2 > 0))
ratio = min(air1_moles / transfer_moles1, air2_moles / transfer_moles2)
if((transfer_moles2 == 0 ) && ( transfer_moles1 > 0))
ratio = air1_moles / transfer_moles1
if((transfer_moles1 == 0 ) && ( transfer_moles2 > 0))
ratio = air2_moles / transfer_moles2
transfer_moles1 *= ratio
transfer_moles2 *= ratio
//Actually transfer the gas
if(transfer_moles1 > 0)
var/datum/gas_mixture/removed1 = air1.remove(transfer_moles1)
air3.merge(removed1)
if(transfer_moles2 > 0)
var/datum/gas_mixture/removed2 = air2.remove(transfer_moles2)
air3.merge(removed2)
if(transfer_moles1)
var/datum/pipeline/parent1 = PARENT1
parent1.update = TRUE
if(transfer_moles2)
var/datum/pipeline/parent2 = PARENT2
parent2.update = TRUE
var/datum/pipeline/parent3 = PARENT3
parent3.update = TRUE
return TRUE
/obj/machinery/atmospherics/components/trinary/mixer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "atmos_mixer", name, 370, 165, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/trinary/mixer/ui_data()
var/data = list()
data["on"] = on
data["set_pressure"] = round(target_pressure)
data["max_pressure"] = round(MAX_OUTPUT_PRESSURE)
data["node1_concentration"] = round(node1_concentration*100)
data["node2_concentration"] = round(node2_concentration*100)
return data
/obj/machinery/atmospherics/components/trinary/mixer/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos")
. = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "max")
pressure = MAX_OUTPUT_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New output pressure (0-[MAX_OUTPUT_PRESSURE] kPa):", name, target_pressure) as num|null
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
target_pressure = Clamp(pressure, 0, MAX_OUTPUT_PRESSURE)
investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", "atmos")
if("node1")
var/value = text2num(params["concentration"])
node1_concentration = max(0, min(1, node1_concentration + value))
node2_concentration = max(0, min(1, node2_concentration - value))
investigate_log("was set to [node1_concentration] % on node 1 by [key_name(usr)]", "atmos")
. = TRUE
if("node2")
var/value = text2num(params["concentration"])
node2_concentration = max(0, min(1, node2_concentration + value))
node1_concentration = max(0, min(1, node1_concentration - value))
investigate_log("was set to [node2_concentration] % on node 2 by [key_name(usr)]", "atmos")
. = TRUE
update_icon()
@@ -0,0 +1,47 @@
/obj/machinery/atmospherics/components/trinary
icon = 'icons/obj/atmospherics/components/trinary_devices.dmi'
dir = SOUTH
initialize_directions = SOUTH|NORTH|WEST
use_power = 1
device_type = TRINARY
var/flipped = 0
/obj/machinery/atmospherics/components/trinary/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions = EAST|NORTH|SOUTH
if(SOUTH)
initialize_directions = SOUTH|WEST|NORTH
if(EAST)
initialize_directions = EAST|WEST|SOUTH
if(WEST)
initialize_directions = WEST|NORTH|EAST
/*
Housekeeping and pipe network stuff
*/
/obj/machinery/atmospherics/components/trinary/atmosinit()
//Mixer:
//1 and 2 is input
//Node 3 is output
//If we flip the mixer, 1 and 3 shall exchange positions
//Filter:
//Node 1 is input
//Node 2 is filtered output
//Node 3 is rest output
//If we flip the filter, 1 and 3 shall exchange positions
var/node1_connect = turn(dir, -180)
var/node2_connect = turn(dir, -90)
var/node3_connect = dir
if(flipped)
node1_connect = turn(node1_connect, 180)
node3_connect = turn(node3_connect, 180)
var/list/node_connects = list(node1_connect, node2_connect, node3_connect)
..(node_connects)
@@ -0,0 +1,271 @@
/obj/machinery/atmospherics/components/unary/cryo_cell
name = "cryo cell"
icon = 'icons/obj/cryogenics.dmi'
icon_state = "cell-off"
density = 1
anchored = 1
var/on = FALSE
state_open = FALSE
var/autoeject = FALSE
var/volume = 100
var/efficiency = 1
var/sleep_factor = 750
var/paralyze_factor = 1000
var/heat_capacity = 20000
var/conduction_coefficient = 0.30
var/obj/item/weapon/reagent_containers/glass/beaker = null
var/reagent_transfer = 0
/obj/machinery/atmospherics/components/unary/cryo_cell/New()
..()
initialize_directions = dir
var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/cryo_tube(null)
B.apply_default_parts(src)
/obj/item/weapon/circuitboard/machine/cryo_tube
name = "circuit board (Cryotube)"
build_path = /obj/machinery/atmospherics/components/unary/cryo_cell
origin_tech = "programming=4;biotech=3;engineering=4;plasmatech=3"
req_components = list(
/obj/item/weapon/stock_parts/matter_bin = 1,
/obj/item/stack/cable_coil = 1,
/obj/item/weapon/stock_parts/console_screen = 1,
/obj/item/stack/sheet/glass = 2)
/obj/machinery/atmospherics/components/unary/cryo_cell/construction()
..(dir, dir)
/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts()
var/C
for(var/obj/item/weapon/stock_parts/matter_bin/M in component_parts)
C += M.rating
efficiency = initial(efficiency) * C
sleep_factor = initial(sleep_factor) * C
paralyze_factor = initial(paralyze_factor) * C
heat_capacity = initial(heat_capacity) / C
conduction_coefficient = initial(conduction_coefficient) * C
/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy()
beaker = null
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon()
if(panel_open)
icon_state = "cell-o"
else if(state_open)
icon_state = "cell-open"
else if(on && is_operational())
if(occupant)
icon_state = "cell-occupied"
else
icon_state = "cell-on"
else
icon_state = "cell-off"
/obj/machinery/atmospherics/components/unary/cryo_cell/process()
..()
if(!on)
return
if(!is_operational())
on = FALSE
update_icon()
return
var/datum/gas_mixture/air1 = AIR1
if(occupant)
if(occupant.health >= 100) // Don't bother with fully healed people.
on = FALSE
update_icon()
playsound(src.loc, 'sound/machines/ding.ogg', volume, 1) // Bug the doctors.
if(autoeject) // Eject if configured.
open_machine()
return
else if(occupant.stat == DEAD) // We don't bother with dead people.
return
if(occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic.
occupant.Sleeping((occupant.bodytemperature / sleep_factor) * 100)
occupant.Paralyse((occupant.bodytemperature / paralyze_factor) * 100)
if(beaker)
if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic.
beaker.reagents.trans_to(occupant, 1, 10 * efficiency) // Transfer reagents, multiplied because cryo magic.
beaker.reagents.reaction(occupant, VAPOR)
air1.gases["o2"][MOLES] -= 2 / efficiency // Lets use gas for this.
if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker).
reagent_transfer = 0
return 1
/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos()
..()
if(!on)
return
var/datum/gas_mixture/air1 = AIR1
if(!NODE1 || !AIR1 || air1.gases["o2"][MOLES] < 5) // Turn off if the machine won't work.
on = FALSE
update_icon()
return
if(occupant)
var/cold_protection = 0
var/mob/living/carbon/human/H = occupant
if(istype(H))
cold_protection = H.get_cold_protection(air1.temperature)
var/temperature_delta = air1.temperature - occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant.
if(abs(temperature_delta) > 1)
var/air_heat_capacity = air1.heat_capacity()
var/heat = ((1 - cold_protection) / 10 + conduction_coefficient) \
* temperature_delta * \
(air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity))
air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB)
occupant.bodytemperature = max(occupant.bodytemperature + heat / heat_capacity, TCMB)
air1.gases["o2"][MOLES] -= 0.5 / efficiency // Magically consume gas? Why not, we run on cryo magic.
/obj/machinery/atmospherics/components/unary/cryo_cell/power_change()
..()
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user)
container_resist(user)
/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine()
if(!state_open && !panel_open)
on = FALSE
..()
if(beaker)
beaker.loc = src
/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user)
if((isnull(user) || istype(user)) && state_open && !panel_open)
..(user)
return occupant
/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/user)
user << "<span class='notice'>You struggle inside the cryotube, kicking the release with your foot... (This will take around 30 seconds.)</span>"
audible_message("<span class='notice'>You hear a thump from [src].</span>")
if(do_after(user, 300))
if(occupant == user) // Check they're still here.
open_machine()
/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user)
..()
if(occupant)
if(on)
user << "Someone's inside [src]!"
else
user << "You can barely make out a form floating in [src]."
else
user << "[src] seems empty."
/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user)
if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser())
return
close_machine(target)
/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/weapon/reagent_containers/glass))
. = 1 //no afterattack
if(!user.drop_item())
return
if(beaker)
user << "<span class='warning'>A beaker is already loaded into [src]!</span>"
return
beaker = I
I.loc = src
user.visible_message("[user] places [I] in [src].", \
"<span class='notice'>You place [I] in [src].</span>")
return
if(!on && !occupant && !state_open)
if(default_deconstruction_screwdriver(user, "cell-o", "cell-off", I))
return
if(exchange_parts(user, I))
return
if(default_change_direction_wrench(user, I))
return
if(default_pry_open(I))
return
if(default_deconstruction_crowbar(I))
return
return ..()
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = notcontained_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data()
var/list/data = list()
data["isOperating"] = on
data["hasOccupant"] = occupant ? 1 : 0
data["isOpen"] = state_open
data["autoEject"] = autoeject
var/list/occupantData = list()
if(occupant)
occupantData["name"] = occupant.name
occupantData["stat"] = occupant.stat
occupantData["health"] = occupant.health
occupantData["maxHealth"] = occupant.maxHealth
occupantData["minHealth"] = config.health_threshold_dead
occupantData["bruteLoss"] = occupant.getBruteLoss()
occupantData["oxyLoss"] = occupant.getOxyLoss()
occupantData["toxLoss"] = occupant.getToxLoss()
occupantData["fireLoss"] = occupant.getFireLoss()
occupantData["bodyTemperature"] = occupant.bodytemperature
data["occupant"] = occupantData
var/datum/gas_mixture/air1 = AIR1
data["cellTemperature"] = round(air1.temperature)
data["isBeakerLoaded"] = beaker ? 1 : 0
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
/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params)
if(..())
return
switch(action)
if("power")
if(on)
on = FALSE
else if(!state_open)
on = TRUE
. = TRUE
if("door")
if(state_open)
close_machine()
else
open_machine()
. = TRUE
if("autoeject")
autoeject = !autoeject
. = TRUE
if("ejectbeaker")
if(beaker)
beaker.loc = loc
beaker = null
. = TRUE
update_icon()
/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user)
return //we don't see the pipe network while inside cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user)
user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through()
return //can't ventcrawl in or out of cryo.
/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes()
return 0 //you can't see the pipe network when inside a cryo cell.
@@ -0,0 +1,20 @@
/obj/machinery/atmospherics/components/unary/generator_input
icon_state = "he_intact"
density = 1
name = "generator input"
desc = "Placeholder"
var/update_cycle
/obj/machinery/atmospherics/components/unary/generator_input/update_icon()
if(NODE1)
icon_state = "intact"
else
icon_state = "exposed"
return
/obj/machinery/atmospherics/components/unary/generator_input/proc/return_exchange_air()
return AIR1
@@ -0,0 +1,69 @@
/obj/machinery/atmospherics/components/unary/heat_exchanger
icon_state = "he_intact"
name = "heat exchanger"
desc = "Exchanges heat between two input gases. Setup for fast heat transfer"
can_unwrench = 1
var/obj/machinery/atmospherics/components/unary/heat_exchanger/partner = null
var/update_cycle
/obj/machinery/atmospherics/components/unary/heat_exchanger/update_icon()
if(NODE1)
icon_state = "he_intact"
var/obj/machinery/atmospherics/node = NODE1
color = node.color
else
icon_state = "he_exposed"
return
/obj/machinery/atmospherics/components/unary/heat_exchanger/atmosinit()
if(!partner)
var/partner_connect = turn(dir,180)
for(var/obj/machinery/atmospherics/components/unary/heat_exchanger/target in get_step(src,partner_connect))
if(target.dir & get_dir(src,target))
partner = target
partner.partner = src
break
..()
/obj/machinery/atmospherics/components/unary/heat_exchanger/process_atmos()
..()
if(!partner)
return 0
if(SSair.times_fired <= update_cycle)
return 0
update_cycle = SSair.times_fired
partner.update_cycle = SSair.times_fired
var/datum/gas_mixture/air_contents = AIR1
var/datum/gas_mixture/partner_air_contents = partner.AIR1
var/air_heat_capacity = air_contents.heat_capacity()
var/other_air_heat_capacity = partner_air_contents.heat_capacity()
var/combined_heat_capacity = other_air_heat_capacity + air_heat_capacity
var/old_temperature = air_contents.temperature
var/other_old_temperature = partner_air_contents.temperature
if(combined_heat_capacity > 0)
var/combined_energy = partner_air_contents.temperature*other_air_heat_capacity + air_heat_capacity*air_contents.temperature
var/new_temperature = combined_energy/combined_heat_capacity
air_contents.temperature = new_temperature
partner_air_contents.temperature = new_temperature
if(abs(old_temperature-air_contents.temperature) > 1)
update_parents()
if(abs(other_old_temperature-partner_air_contents.temperature) > 1)
partner.update_parents()
return 1
@@ -0,0 +1,141 @@
/obj/machinery/atmospherics/components/unary/outlet_injector
icon_state = "inje_map"
use_power = 1
name = "air injector"
desc = "Has a valve and pump attached to it"
var/on = 0
var/injecting = 0
var/volume_rate = 50
var/frequency = 0
var/id = null
var/datum/radio_frequency/radio_connection
level = 1
/obj/machinery/atmospherics/components/unary/outlet_injector/Destroy()
if(SSradio)
SSradio.remove_object(src,frequency)
return ..()
/obj/machinery/atmospherics/components/unary/outlet_injector/on
on = 1
/obj/machinery/atmospherics/components/unary/outlet_injector/update_icon_nopipes()
if(!NODE1 || !on || stat & (NOPOWER|BROKEN))
icon_state = "inje_off"
return
icon_state = "inje_on"
/obj/machinery/atmospherics/components/unary/outlet_injector/power_change()
var/old_stat = stat
..()
if(old_stat != stat)
update_icon()
/obj/machinery/atmospherics/components/unary/outlet_injector/process_atmos()
..()
injecting = 0
if(!on || stat & NOPOWER)
return 0
var/datum/gas_mixture/air_contents = AIR1
if(air_contents.temperature > 0)
var/transfer_moles = (air_contents.return_pressure())*volume_rate/(air_contents.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
loc.assume_air(removed)
air_update_turf()
update_parents()
return 1
/obj/machinery/atmospherics/components/unary/outlet_injector/proc/inject()
if(on || injecting)
return 0
var/datum/gas_mixture/air_contents = AIR1
injecting = 1
if(air_contents.temperature > 0)
var/transfer_moles = (air_contents.return_pressure())*volume_rate/(air_contents.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
loc.assume_air(removed)
update_parents()
flick("inje_inject", src)
/obj/machinery/atmospherics/components/unary/outlet_injector/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency)
/obj/machinery/atmospherics/components/unary/outlet_injector/proc/broadcast_status()
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = list(
"tag" = id,
"device" = "AO",
"power" = on,
"volume_rate" = volume_rate,
//"timestamp" = world.time,
"sigtype" = "status"
)
radio_connection.post_signal(src, signal)
return 1
/obj/machinery/atmospherics/components/unary/outlet_injector/atmosinit()
set_frequency(frequency)
broadcast_status()
..()
/obj/machinery/atmospherics/components/unary/outlet_injector/receive_signal(datum/signal/signal)
if(!signal.data["tag"] || (signal.data["tag"] != id) || (signal.data["sigtype"]!="command"))
return 0
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("inject" in signal.data)
spawn inject()
return
if("set_volume_rate" in signal.data)
var/number = text2num(signal.data["set_volume_rate"])
var/datum/gas_mixture/air_contents = AIR1
volume_rate = Clamp(number, 0, air_contents.volume)
if("status" in signal.data)
spawn(2)
broadcast_status()
return //do not update_icon
//log_admin("DEBUG \[[world.timeofday]\]: outlet_injector/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
//return
spawn(2)
broadcast_status()
update_icon()
@@ -0,0 +1,54 @@
/obj/machinery/atmospherics/components/unary/oxygen_generator
icon_state = "o2gen_map"
name = "oxygen generator"
desc = "Generates oxygen"
dir = SOUTH
initialize_directions = SOUTH
var/on = 0
var/oxygen_content = 10
/obj/machinery/atmospherics/components/unary/oxygen_generator/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "scrub_cap", initialize_directions)) //it works for now
if(!NODE1 || !on || stat & BROKEN)
icon_state = "o2gen_off"
return
else
icon_state = "o2gen_on"
/obj/machinery/atmospherics/components/unary/oxygen_generator/New()
..()
var/datum/gas_mixture/air_contents = AIR1
air_contents.volume = 50
AIR1 = air_contents
/obj/machinery/atmospherics/components/unary/oxygen_generator/process_atmos()
..()
if(!on)
return 0
var/datum/gas_mixture/air_contents = AIR1
var/total_moles = air_contents.total_moles()
if(total_moles < oxygen_content)
var/current_heat_capacity = air_contents.heat_capacity()
var/added_oxygen = oxygen_content - total_moles
air_contents.temperature = (current_heat_capacity*air_contents.temperature + 20*added_oxygen*T0C)/(current_heat_capacity+20*added_oxygen)
air_contents.assert_gas("o2")
air_contents.gases["o2"][MOLES] += added_oxygen
update_parents()
return 1
@@ -0,0 +1,40 @@
/obj/machinery/atmospherics/components/unary/portables_connector
name = "connector port"
desc = "For connecting portables devices related to atmospherics control."
icon = 'icons/obj/atmospherics/components/unary_devices.dmi'
icon_state = "connector_map" //Only for mapping purposes, so mappers can see direction
can_unwrench = 1
var/obj/machinery/portable_atmospherics/connected_device
use_power = 0
level = 0
/obj/machinery/atmospherics/components/unary/portables_connector/New()
..()
var/datum/gas_mixture/air_contents = AIR1
air_contents.volume = 0
/obj/machinery/atmospherics/components/unary/portables_connector/visible
level = 2
/obj/machinery/atmospherics/components/unary/portables_connector/process_atmos()
if(!connected_device)
return
update_parents()
/obj/machinery/atmospherics/components/unary/portables_connector/Destroy()
if(connected_device)
connected_device.disconnect()
return ..()
/obj/machinery/atmospherics/components/unary/portables_connector/can_unwrench(mob/user)
if(..())
if(connected_device)
user << "<span class='warning'>You cannot unwrench this [src], detach [connected_device] first!</span>"
else
return 1
/obj/machinery/atmospherics/components/unary/portables_connector/portableConnectorReturnAir()
return connected_device.portableConnectorReturnAir()
/obj/proc/portableConnectorReturnAir()
@@ -0,0 +1,49 @@
#define AIR_CONTENTS (25*ONE_ATMOSPHERE)*(air_contents.volume)/(R_IDEAL_GAS_EQUATION*air_contents.temperature)
/obj/machinery/atmospherics/components/unary/tank
icon = 'icons/obj/atmospherics/pipes/pressure_tank.dmi'
icon_state = "generic"
name = "pressure tank"
desc = "A large vessel containing pressurized gas."
var/volume = 10000 //in liters, 1 meters by 1 meters by 2 meters
density = 1
var/gas_type = 0
/obj/machinery/atmospherics/components/unary/tank/New()
..()
var/datum/gas_mixture/air_contents = AIR1
air_contents.volume = volume
air_contents.temperature = T20C
if(gas_type)
air_contents.assert_gas(gas_type)
air_contents.gases[gas_type][MOLES] = AIR_CONTENTS
name = "[name] ([air_contents.gases[gas_type][GAS_META][META_GAS_NAME]])"
/obj/machinery/atmospherics/components/unary/tank/carbon_dioxide
gas_type = "co2"
/obj/machinery/atmospherics/components/unary/tank/toxins
icon_state = "orange"
gas_type = "plasma"
/obj/machinery/atmospherics/components/unary/tank/oxygen_agent_b
icon_state = "orange_2"
gas_type = "agent_b"
/obj/machinery/atmospherics/components/unary/tank/oxygen
icon_state = "blue"
gas_type = "o2"
/obj/machinery/atmospherics/components/unary/tank/nitrogen
icon_state = "red"
gas_type = "n2"
/obj/machinery/atmospherics/components/unary/tank/air
icon_state = "grey"
name = "pressure tank (Air)"
/obj/machinery/atmospherics/components/unary/tank/air/New()
..()
var/datum/gas_mixture/air_contents = AIR1
air_contents.assert_gases("o2", "n2")
air_contents.gases["o2"][MOLES] = AIR_CONTENTS * 0.2
air_contents.gases["n2"][MOLES] = AIR_CONTENTS * 0.8
@@ -0,0 +1,232 @@
/obj/machinery/atmospherics/components/unary/thermomachine
name = "thermomachine"
desc = "Heats or cools gas in connected pipes."
icon_state = "cold_map"
var/icon_state_on = "cold_on"
var/icon_state_open = "cold_off"
density = TRUE
anchored = TRUE
var/on = FALSE
var/min_temperature = 0
var/max_temperature = 0
var/target_temperature = T20C
var/heat_capacity = 0
var/interactive = TRUE // So mapmakers can disable interaction.
/obj/machinery/atmospherics/components/unary/thermomachine/New()
..()
initialize_directions = dir
var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/thermomachine(null)
B.apply_default_parts(src)
/obj/item/weapon/circuitboard/machine/thermomachine
name = "circuit board (Thermomachine)"
desc = "You can use a screwdriver to switch between heater and freezer."
origin_tech = "programming=3;plasmatech=3"
req_components = list(
/obj/item/weapon/stock_parts/matter_bin = 2,
/obj/item/weapon/stock_parts/micro_laser = 2,
/obj/item/stack/cable_coil = 1,
/obj/item/weapon/stock_parts/console_screen = 1)
/obj/item/weapon/circuitboard/machine/thermomachine/attackby(obj/item/I, mob/user, params)
var/obj/item/weapon/circuitboard/machine/freezer = /obj/item/weapon/circuitboard/machine/thermomachine/freezer
var/obj/item/weapon/circuitboard/machine/heater = /obj/item/weapon/circuitboard/machine/thermomachine/heater
var/obj/item/weapon/circuitboard/machine/newtype
if(istype(I, /obj/item/weapon/screwdriver))
var/new_setting = "Heater"
playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1)
if(build_path == initial(heater.build_path))
newtype = freezer
new_setting = "Freezer"
else
newtype = heater
name = initial(newtype.name)
build_path = initial(newtype.build_path)
user << "<span class='notice'>You change the circuitboard setting to \"[new_setting]\".</span>"
else
return ..()
/obj/machinery/atmospherics/components/unary/thermomachine/construction()
..(dir,dir)
/obj/machinery/atmospherics/components/unary/thermomachine/RefreshParts()
var/B
for(var/obj/item/weapon/stock_parts/matter_bin/M in component_parts)
B += M.rating
heat_capacity = 5000 * ((B - 1) ** 2)
/obj/machinery/atmospherics/components/unary/thermomachine/update_icon()
if(panel_open)
icon_state = icon_state_open
else if(on && is_operational())
icon_state = icon_state_on
else
icon_state = initial(icon_state)
return
/obj/machinery/atmospherics/components/unary/thermomachine/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage(icon, "scrub_cap", initialize_directions))
/obj/machinery/atmospherics/components/unary/thermomachine/process_atmos()
..()
if(!on || !NODE1)
return
var/datum/gas_mixture/air_contents = AIR1
var/air_heat_capacity = air_contents.heat_capacity()
var/combined_heat_capacity = heat_capacity + air_heat_capacity
var/old_temperature = air_contents.temperature
if(combined_heat_capacity > 0)
var/combined_energy = heat_capacity * target_temperature + air_heat_capacity * air_contents.temperature
air_contents.temperature = combined_energy/combined_heat_capacity
var/temperature_delta= abs(old_temperature - air_contents.temperature)
if(temperature_delta > 1)
active_power_usage = (heat_capacity * temperature_delta) / 10 + idle_power_usage
update_parents()
else
active_power_usage = idle_power_usage
return 1
/obj/machinery/atmospherics/components/unary/thermomachine/power_change()
..()
update_icon()
/obj/machinery/atmospherics/components/unary/thermomachine/attackby(obj/item/I, mob/user, params)
if(!(on || state_open))
if(default_deconstruction_screwdriver(user, icon_state_open, initial(icon_state), I))
return
if(exchange_parts(user, I))
return
if(default_change_direction_wrench(user, I))
return
if(default_deconstruction_crowbar(I))
return
return ..()
/obj/machinery/atmospherics/components/unary/thermomachine/default_change_direction_wrench(mob/user, obj/item/weapon/wrench/W)
if(!..())
return 0
SetInitDirections()
var/obj/machinery/atmospherics/node = NODE1
if(node)
node.disconnect(src)
NODE1 = null
nullifyPipenet(PARENT1)
atmosinit()
node = NODE1
if(node)
node.atmosinit()
node.addMember(src)
build_network()
return 1
/obj/machinery/atmospherics/components/unary/thermomachine/ui_status(mob/user)
if(interactive)
return ..()
return UI_CLOSE
/obj/machinery/atmospherics/components/unary/thermomachine/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "thermomachine", name, 400, 240, master_ui, state)
ui.open()
/obj/machinery/atmospherics/components/unary/thermomachine/ui_data(mob/user)
var/list/data = list()
data["on"] = on
data["min"] = min_temperature
data["max"] = max_temperature
data["target"] = target_temperature
data["initial"] = initial(target_temperature)
var/datum/gas_mixture/air1 = AIR1
data["temperature"] = air1.temperature
data["pressure"] = air1.return_pressure()
return data
/obj/machinery/atmospherics/components/unary/thermomachine/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
use_power = 1 + on
investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", "atmos")
. = TRUE
if("target")
var/target = params["target"]
var/adjust = text2num(params["adjust"])
if(target == "input")
target = input("Set new target ([min_temperature]-[max_temperature] K):", name, target_temperature) as num|null
if(!isnull(target) && !..())
. = TRUE
else if(adjust)
target = target_temperature + adjust
. = TRUE
else if(text2num(target) != null)
target = text2num(target)
. = TRUE
if(.)
target_temperature = Clamp(target, min_temperature, max_temperature)
investigate_log("was set to [target_temperature] K by [key_name(usr)]", "atmos")
update_icon()
/obj/machinery/atmospherics/components/unary/thermomachine/freezer
name = "freezer"
icon = 'icons/obj/Cryogenic2.dmi'
icon_state = "freezer"
icon_state_on = "freezer_1"
icon_state_open = "freezer-o"
max_temperature = T20C
min_temperature = 170
/obj/machinery/atmospherics/components/unary/thermomachine/freezer/New()
..()
var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/thermomachine/freezer(null)
B.apply_default_parts(src)
/obj/item/weapon/circuitboard/machine/thermomachine/freezer
name = "circuit board (Freezer)"
build_path = /obj/machinery/atmospherics/components/unary/thermomachine/freezer
/obj/machinery/atmospherics/components/unary/thermomachine/freezer/RefreshParts()
..()
var/L
for(var/obj/item/weapon/stock_parts/micro_laser/M in component_parts)
L += M.rating
min_temperature = max(T0C - (initial(min_temperature) + L * 15), TCMB)
/obj/machinery/atmospherics/components/unary/thermomachine/heater
name = "heater"
icon = 'icons/obj/Cryogenic2.dmi'
icon_state = "heater"
icon_state_on = "heater_1"
icon_state_open = "heater-o"
max_temperature = 140
min_temperature = T20C
/obj/machinery/atmospherics/components/unary/thermomachine/heater/New()
..()
var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/thermomachine/heater(null)
B.apply_default_parts(src)
/obj/item/weapon/circuitboard/machine/thermomachine/heater
name = "circuit board (Heater)"
build_path = /obj/machinery/atmospherics/components/unary/thermomachine/heater
/obj/machinery/atmospherics/components/unary/thermomachine/heater/RefreshParts()
..()
var/L
for(var/obj/item/weapon/stock_parts/micro_laser/M in component_parts)
L += M.rating
max_temperature = T20C + (initial(max_temperature) * L)
@@ -0,0 +1,17 @@
/obj/machinery/atmospherics/components/unary
icon = 'icons/obj/atmospherics/components/unary_devices.dmi'
dir = SOUTH
initialize_directions = SOUTH
device_type = UNARY
/obj/machinery/atmospherics/components/unary/SetInitDirections()
initialize_directions = dir
/*
Iconnery
*/
/obj/machinery/atmospherics/components/unary/hide(intact)
update_icon()
..(intact)
@@ -0,0 +1,301 @@
#define EXT_BOUND 1
#define INT_BOUND 2
#define NO_BOUND 3
#define SIPHONING 0
#define RELEASING 1
/obj/machinery/atmospherics/components/unary/vent_pump
name = "air vent"
desc = "Has a valve and pump attached to it"
icon_state = "vent_map"
use_power = 1
can_unwrench = 1
welded = 0
level = 1
var/id_tag = null
var/on = 0
var/pump_direction = RELEASING
var/pressure_checks = EXT_BOUND
var/external_pressure_bound = ONE_ATMOSPHERE
var/internal_pressure_bound = 0
//EXT_BOUND: Do not pass external_pressure_bound
//INT_BOUND: Do not pass internal_pressure_bound
//NO_BOUND: Do not pass either
var/frequency = 1439
var/datum/radio_frequency/radio_connection
var/radio_filter_out
var/radio_filter_in
/obj/machinery/atmospherics/components/unary/vent_pump/on
on = 1
icon_state = "vent_out"
/obj/machinery/atmospherics/components/unary/vent_pump/siphon
pump_direction = SIPHONING
/obj/machinery/atmospherics/components/unary/vent_pump/siphon/on
on = 1
icon_state = "vent_in"
/obj/machinery/atmospherics/components/unary/vent_pump/New()
..()
if(!id_tag)
assign_uid()
id_tag = num2text(uid)
/obj/machinery/atmospherics/components/unary/vent_pump/Destroy()
var/area/A = get_area_master(src)
A.air_vent_names -= id_tag
A.air_vent_info -= id_tag
if(SSradio)
SSradio.remove_object(src,frequency)
radio_connection = null
return ..()
/obj/machinery/atmospherics/components/unary/vent_pump/high_volume
name = "large air vent"
power_channel = EQUIP
/obj/machinery/atmospherics/components/unary/vent_pump/high_volume/New()
..()
var/datum/gas_mixture/air_contents = AIR1
air_contents.volume = 1000
/obj/machinery/atmospherics/components/unary/vent_pump/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "vent_cap", initialize_directions))
if(welded)
icon_state = "vent_welded"
return
if(!NODE1 || !on || stat & (NOPOWER|BROKEN))
icon_state = "vent_off"
return
if(pump_direction & RELEASING)
icon_state = "vent_out"
else //pump_direction == SIPHONING
icon_state = "vent_in"
/obj/machinery/atmospherics/components/unary/vent_pump/process_atmos()
..()
if(stat & (NOPOWER|BROKEN))
return
if (!NODE1)
on = 0
if(!on || welded)
return 0
var/datum/gas_mixture/air_contents = AIR1
var/datum/gas_mixture/environment = loc.return_air()
var/environment_pressure = environment.return_pressure()
if(pump_direction & RELEASING) //internal -> external
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure))
if(pressure_checks&INT_BOUND)
pressure_delta = min(pressure_delta, (air_contents.return_pressure() - internal_pressure_bound))
if(pressure_delta > 0)
if(air_contents.temperature > 0)
var/transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
loc.assume_air(removed)
air_update_turf()
else //external -> internal
var/pressure_delta = 10000
if(pressure_checks&EXT_BOUND)
pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound))
if(pressure_checks&INT_BOUND)
pressure_delta = min(pressure_delta, (internal_pressure_bound - air_contents.return_pressure()))
if(pressure_delta > 0)
if(environment.temperature > 0)
var/transfer_moles = pressure_delta*air_contents.volume/(environment.temperature * R_IDEAL_GAS_EQUATION)
var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
if (isnull(removed)) //in space
return
air_contents.merge(removed)
air_update_turf()
update_parents()
return 1
//Radio remote control
/obj/machinery/atmospherics/components/unary/vent_pump/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
if(frequency)
radio_connection = SSradio.add_object(src, frequency,radio_filter_in)
/obj/machinery/atmospherics/components/unary/vent_pump/proc/broadcast_status()
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = list(
"tag" = id_tag,
"frequency" = frequency,
"device" = "VP",
"timestamp" = world.time,
"power" = on,
"direction" = pump_direction ? "release" : "siphon",
"checks" = pressure_checks,
"internal" = internal_pressure_bound,
"external" = external_pressure_bound,
"sigtype" = "status"
)
var/area/A = get_area_master(src)
if(!A.air_vent_names[id_tag])
name = "\improper [A.name] vent pump #[A.air_vent_names.len + 1]"
A.air_vent_names[id_tag] = name
A.air_vent_info[id_tag] = signal.data
radio_connection.post_signal(src, signal, radio_filter_out)
return 1
/obj/machinery/atmospherics/components/unary/vent_pump/atmosinit()
//some vents work his own spesial way
radio_filter_in = frequency==1439?(RADIO_FROM_AIRALARM):null
radio_filter_out = frequency==1439?(RADIO_TO_AIRALARM):null
if(frequency)
set_frequency(frequency)
broadcast_status()
..()
/obj/machinery/atmospherics/components/unary/vent_pump/receive_signal(datum/signal/signal)
if(stat & (NOPOWER|BROKEN))
return
//log_admin("DEBUG \[[world.timeofday]\]: /obj/machinery/atmospherics/components/unary/vent_pump/receive_signal([signal.debug_print()])")
if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
return 0
if("purge" in signal.data)
pressure_checks &= ~EXT_BOUND
pump_direction = SIPHONING
if("stabalize" in signal.data)
pressure_checks |= EXT_BOUND
pump_direction = RELEASING
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("checks" in signal.data)
pressure_checks = text2num(signal.data["checks"])
if("checks_toggle" in signal.data)
pressure_checks = (pressure_checks?0:NO_BOUND)
if("direction" in signal.data)
pump_direction = text2num(signal.data["direction"])
if("set_internal_pressure" in signal.data)
internal_pressure_bound = Clamp(text2num(signal.data["set_internal_pressure"]),0,ONE_ATMOSPHERE*50)
if("set_external_pressure" in signal.data)
external_pressure_bound = Clamp(text2num(signal.data["set_external_pressure"]),0,ONE_ATMOSPHERE*50)
if("reset_external_pressure" in signal.data)
external_pressure_bound = ONE_ATMOSPHERE
if("adjust_internal_pressure" in signal.data)
internal_pressure_bound = Clamp(internal_pressure_bound + text2num(signal.data["adjust_internal_pressure"]),0,ONE_ATMOSPHERE*50)
if("adjust_external_pressure" in signal.data)
external_pressure_bound = Clamp(external_pressure_bound + text2num(signal.data["adjust_external_pressure"]),0,ONE_ATMOSPHERE*50)
if("init" in signal.data)
name = signal.data["init"]
return
if("status" in signal.data)
broadcast_status()
return //do not update_icon
//log_admin("DEBUG \[[world.timeofday]\]: vent_pump/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
broadcast_status()
update_icon()
return
/obj/machinery/atmospherics/components/unary/vent_pump/attackby(obj/item/W, mob/user, params)
if(istype(W, /obj/item/weapon/weldingtool))
var/obj/item/weapon/weldingtool/WT = W
if (WT.remove_fuel(0,user))
playsound(loc, 'sound/items/Welder.ogg', 40, 1)
user << "<span class='notice'>You begin welding the vent...</span>"
if(do_after(user, 20/W.toolspeed, target = src))
if(!src || !WT.isOn()) return
playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1)
if(!welded)
user.visible_message("[user] welds the vent shut.", "<span class='notice'>You weld the vent shut.</span>", "<span class='italics'>You hear welding.</span>")
welded = 1
else
user.visible_message("[user] unwelds the vent.", "<span class='notice'>You unweld the vent.</span>", "<span class='italics'>You hear welding.</span>")
welded = 0
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
return 0
else
return ..()
/obj/machinery/atmospherics/components/unary/vent_pump/can_unwrench(mob/user)
if(..())
if(!(stat & NOPOWER) && on)
user << "<span class='warning'>You cannot unwrench this [src], turn it off first!</span>"
else
return 1
/obj/machinery/atmospherics/components/unary/vent_pump/examine(mob/user)
..()
if(welded)
user << "It seems welded shut."
/obj/machinery/atmospherics/components/unary/vent_pump/power_change()
..()
update_icon_nopipes()
/obj/machinery/atmospherics/components/unary/vent_pump/can_crawl_through()
return !welded
/obj/machinery/atmospherics/components/unary/vent_pump/attack_alien(mob/user)
if(!welded || !(do_after(user, 20, target = src)))
return
user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the vent", "You hear loud scraping noises.")
welded = 0
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)
#undef INT_BOUND
#undef EXT_BOUND
#undef NO_BOUND
#undef SIPHONING
#undef RELEASING
@@ -0,0 +1,352 @@
#define SIPHONING 0
#define SCRUBBING 1
/obj/machinery/atmospherics/components/unary/vent_scrubber
name = "air scrubber"
desc = "Has a valve and pump attached to it"
icon_state = "scrub_map"
use_power = 1
idle_power_usage = 10
active_power_usage = 60
can_unwrench = 1
welded = 0
level = 1
var/id_tag = null
var/on = 0
var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing
var/scrub_CO2 = 1
var/scrub_Toxins = 0
var/scrub_N2O = 0
var/scrub_BZ = 0
var/volume_rate = 200
var/widenet = 0 //is this scrubber acting on the 3x3 area around it.
var/list/turf/adjacent_turfs = list()
var/frequency = 1439
var/datum/radio_frequency/radio_connection
var/radio_filter_out
var/radio_filter_in
/obj/machinery/atmospherics/components/unary/vent_scrubber/New()
..()
if(!id_tag)
assign_uid()
id_tag = num2text(uid)
/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy()
var/area/A = get_area_master(src)
A.air_scrub_names -= id_tag
A.air_scrub_info -= id_tag
if(SSradio)
SSradio.remove_object(src,frequency)
radio_connection = null
for(var/I in adjacent_turfs)
I = null
return ..()
/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power()
if(!powered(power_channel))
return 0
if(!on || welded)
return 0
if(stat & (NOPOWER|BROKEN))
return 0
var/amount = idle_power_usage
if(scrubbing & SCRUBBING)
if(scrub_CO2)
amount += idle_power_usage
if(scrub_Toxins)
amount += idle_power_usage
if(scrub_N2O)
amount += idle_power_usage
if(scrub_BZ)
amount += idle_power_usage
else //scrubbing == SIPHONING
amount = active_power_usage
if(widenet)
amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2))
use_power(amount, power_channel)
return 1
/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes()
cut_overlays()
if(showpipe)
add_overlay(getpipeimage('icons/obj/atmospherics/components/unary_devices.dmi', "scrub_cap", initialize_directions))
if(welded)
icon_state = "scrub_welded"
return
if(!NODE1 || !on || stat & (NOPOWER|BROKEN))
icon_state = "scrub_off"
return
if(scrubbing & SCRUBBING)
icon_state = "scrub_on"
else //scrubbing == SIPHONING
icon_state = "scrub_purge"
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency, radio_filter_in)
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/broadcast_status()
if(!radio_connection)
return 0
var/datum/signal/signal = new
signal.transmission_method = 1 //radio signal
signal.source = src
signal.data = list(
"tag" = id_tag,
"frequency" = frequency,
"device" = "VS",
"timestamp" = world.time,
"power" = on,
"scrubbing" = scrubbing,
"widenet" = widenet,
"filter_co2" = scrub_CO2,
"filter_toxins" = scrub_Toxins,
"filter_n2o" = scrub_N2O,
"filter_bz" = scrub_BZ,
"sigtype" = "status"
)
var/area/A = get_area_master(src)
if(!A.air_scrub_names[id_tag])
name = "\improper [A.name] air scrubber #[A.air_scrub_names.len + 1]"
A.air_scrub_names[id_tag] = name
A.air_scrub_info[id_tag] = signal.data
radio_connection.post_signal(src, signal, radio_filter_out)
return 1
/obj/machinery/atmospherics/components/unary/vent_scrubber/atmosinit()
radio_filter_in = frequency==initial(frequency)?(RADIO_FROM_AIRALARM):null
radio_filter_out = frequency==initial(frequency)?(RADIO_TO_AIRALARM):null
if(frequency)
set_frequency(frequency)
broadcast_status()
check_turfs()
..()
/obj/machinery/atmospherics/components/unary/vent_scrubber/process_atmos()
..()
if(stat & (NOPOWER|BROKEN))
return
if (!NODE1)
on = 0
if(!on || welded)
return 0
scrub(loc)
if(widenet)
for (var/turf/tile in adjacent_turfs)
scrub(tile)
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile)
if (!istype(tile))
return 0
var/datum/gas_mixture/environment = tile.return_air()
var/datum/gas_mixture/air_contents = AIR1
var/list/env_gases = environment.gases
if(scrubbing & SCRUBBING)
var/should_we_scrub = FALSE
for(var/id in env_gases)
if(id == "n2" || id == "o2")
continue
if(env_gases[id][MOLES])
should_we_scrub = TRUE
break
if(should_we_scrub)
var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles()
//Take a gas sample
var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
var/list/removed_gases = removed.gases
if (isnull(removed)) //in space
return
//Filter it
var/datum/gas_mixture/filtered_out = new
var/list/filtered_gases = filtered_out.gases
filtered_out.temperature = removed.temperature
if(scrub_Toxins && removed_gases["plasma"])
filtered_out.assert_gas("plasma")
filtered_gases["plasma"][MOLES] = removed_gases["plasma"][MOLES]
removed.gases["plasma"][MOLES] = 0
if(scrub_CO2 && removed_gases["co2"])
filtered_out.assert_gas("co2")
filtered_out.gases["co2"][MOLES] = removed_gases["co2"][MOLES]
removed.gases["co2"][MOLES] = 0
if(removed_gases["agent_b"])
filtered_out.assert_gas("agent_b")
filtered_out.gases["agent_b"][MOLES] = removed_gases["agent_b"][MOLES]
removed.gases["agent_b"][MOLES] = 0
if(scrub_N2O && removed_gases["n2o"])
filtered_out.assert_gas("n2o")
filtered_out.gases["n2o"][MOLES] = removed_gases["n2o"][MOLES]
removed.gases["n2o"][MOLES] = 0
if(scrub_BZ && removed_gases["bz"])
filtered_out.assert_gas("bz")
filtered_out.gases["bz"][MOLES] = removed_gases["bz"][MOLES]
removed.gases["bz"][MOLES] = 0
removed.garbage_collect()
//Remix the resulting gases
air_contents.merge(filtered_out)
tile.assume_air(removed)
tile.air_update_turf()
else //Just siphoning all air
if (air_contents.return_pressure()>=50*ONE_ATMOSPHERE)
return
var/transfer_moles = environment.total_moles()*(volume_rate/environment.volume)
var/datum/gas_mixture/removed = tile.remove_air(transfer_moles)
air_contents.merge(removed)
tile.air_update_turf()
update_parents()
return 1
//There is no easy way for an object to be notified of changes to atmos can pass flags
// So we check every machinery process (2 seconds)
/obj/machinery/atmospherics/components/unary/vent_scrubber/process()
if (widenet)
check_turfs()
//we populate a list of turfs with nonatmos-blocked cardinal turfs AND
// diagonal turfs that can share atmos with *both* of the cardinal turfs
/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/check_turfs()
adjacent_turfs.Cut()
var/turf/T = get_turf(src)
if(istype(T))
adjacent_turfs = T.GetAtmosAdjacentTurfs(alldir = 1)
/obj/machinery/atmospherics/components/unary/vent_scrubber/receive_signal(datum/signal/signal)
if(stat & (NOPOWER|BROKEN))
return
if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
return 0
if("power" in signal.data)
on = text2num(signal.data["power"])
if("power_toggle" in signal.data)
on = !on
if("widenet" in signal.data)
widenet = text2num(signal.data["widenet"])
if("toggle_widenet" in signal.data)
widenet = !widenet
if("scrubbing" in signal.data)
scrubbing = text2num(signal.data["scrubbing"])
if("toggle_scrubbing" in signal.data)
scrubbing = !scrubbing
if("co2_scrub" in signal.data)
scrub_CO2 = text2num(signal.data["co2_scrub"])
if("toggle_co2_scrub" in signal.data)
scrub_CO2 = !scrub_CO2
if("tox_scrub" in signal.data)
scrub_Toxins = text2num(signal.data["tox_scrub"])
if("toggle_tox_scrub" in signal.data)
scrub_Toxins = !scrub_Toxins
if("n2o_scrub" in signal.data)
scrub_N2O = text2num(signal.data["n2o_scrub"])
if("toggle_n2o_scrub" in signal.data)
scrub_N2O = !scrub_N2O
if("bz_scrub" in signal.data)
scrub_BZ = text2num(signal.data["bz_scrub"])
if("toggle_bz_scrub" in signal.data)
scrub_BZ = !scrub_BZ
if("init" in signal.data)
name = signal.data["init"]
return
if("status" in signal.data)
broadcast_status()
return //do not update_icon
broadcast_status()
update_icon()
return
/obj/machinery/atmospherics/components/unary/vent_scrubber/power_change()
..()
update_icon_nopipes()
/obj/machinery/atmospherics/components/unary/vent_scrubber/attackby(obj/item/weapon/W, mob/user, params)
if(istype(W, /obj/item/weapon/weldingtool))
var/obj/item/weapon/weldingtool/WT = W
if(WT.remove_fuel(0,user))
playsound(loc, 'sound/items/Welder.ogg', 40, 1)
user << "<span class='notice'>Now welding the scrubber.</span>"
if(do_after(user, 20/W.toolspeed, target = src))
if(!src || !WT.isOn())
return
playsound(src.loc, 'sound/items/Welder2.ogg', 50, 1)
if(!welded)
user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.")
welded = 1
else
user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.")
welded = 0
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
return 0
else
return ..()
/obj/machinery/atmospherics/components/unary/vent_scrubber/can_unwrench(mob/user)
if(..())
if (!(stat & NOPOWER) && on)
user << "<span class='warning'>You cannot unwrench this [src], turn it off first!</span>"
else
return 1
/obj/machinery/atmospherics/components/unary/vent_scrubber/can_crawl_through()
return !welded
/obj/machinery/atmospherics/components/unary/vent_scrubber/attack_alien(mob/user)
if(!welded || !(do_after(user, 20, target = src)))
return
user.visible_message("[user] furiously claws at [src]!", "You manage to clear away the stuff blocking the scrubber.", "You hear loud scraping noises.")
welded = 0
update_icon()
pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir)
playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1)
#undef SIPHONING
#undef SCRUBBING
@@ -0,0 +1,236 @@
/datum/pipeline
var/datum/gas_mixture/air
var/list/datum/gas_mixture/other_airs = list()
var/list/obj/machinery/atmospherics/pipe/members = list()
var/list/obj/machinery/atmospherics/components/other_atmosmch = list()
var/update = 1
/datum/pipeline/New()
SSair.networks += src
/datum/pipeline/Destroy()
SSair.networks -= src
if(air && air.volume)
temporarily_store_air()
for(var/obj/machinery/atmospherics/pipe/P in members)
P.parent = null
for(var/obj/machinery/atmospherics/components/C in other_atmosmch)
C.nullifyPipenet(src)
return ..()
/datum/pipeline/process()
if(update)
update = 0
reconcile_air()
update = air.react()
var/pipenetwarnings = 10
/datum/pipeline/proc/build_pipeline(obj/machinery/atmospherics/base)
var/volume = 0
if(istype(base, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/E = base
volume = E.volume
members += E
if(E.air_temporary)
air = E.air_temporary
E.air_temporary = null
else
addMachineryMember(base)
if(!air)
air = new
var/list/possible_expansions = list(base)
while(possible_expansions.len>0)
for(var/obj/machinery/atmospherics/borderline in possible_expansions)
var/list/result = borderline.pipeline_expansion(src)
if(result.len>0)
for(var/obj/machinery/atmospherics/P in result)
if(istype(P, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/item = P
if(!members.Find(item))
if(item.parent)
if(pipenetwarnings > 0)
warning("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z])")
pipenetwarnings -= 1
if(pipenetwarnings == 0)
warning("build_pipeline(): further messages about pipenets will be supressed")
members += item
possible_expansions += item
volume += item.volume
item.parent = src
if(item.air_temporary)
air.merge(item.air_temporary)
item.air_temporary = null
else
P.setPipenet(src, borderline)
addMachineryMember(P)
possible_expansions -= borderline
air.volume = volume
/datum/pipeline/proc/addMachineryMember(obj/machinery/atmospherics/components/C)
other_atmosmch |= C
var/datum/gas_mixture/G = C.returnPipenetAir(src)
other_airs |= G
/datum/pipeline/proc/addMember(obj/machinery/atmospherics/A, obj/machinery/atmospherics/N)
if(istype(A, /obj/machinery/atmospherics/pipe))
var/obj/machinery/atmospherics/pipe/P = A
P.parent = src
var/list/adjacent = P.pipeline_expansion()
for(var/obj/machinery/atmospherics/pipe/I in adjacent)
if(I.parent == src)
continue
var/datum/pipeline/E = I.parent
merge(E)
if(!members.Find(P))
members += P
air.volume += P.volume
else
A.setPipenet(src, N)
addMachineryMember(A)
/datum/pipeline/proc/merge(datum/pipeline/E)
air.volume += E.air.volume
members.Add(E.members)
for(var/obj/machinery/atmospherics/pipe/S in E.members)
S.parent = src
air.merge(E.air)
for(var/obj/machinery/atmospherics/components/C in E.other_atmosmch)
C.replacePipenet(E, src)
other_atmosmch.Add(E.other_atmosmch)
other_airs.Add(E.other_airs)
E.members.Cut()
E.other_atmosmch.Cut()
qdel(E)
/obj/machinery/atmospherics/proc/addMember(obj/machinery/atmospherics/A)
return
/obj/machinery/atmospherics/pipe/addMember(obj/machinery/atmospherics/A)
parent.addMember(A, src)
/obj/machinery/atmospherics/components/addMember(obj/machinery/atmospherics/A)
var/datum/pipeline/P = returnPipenet(A)
P.addMember(A, src)
/datum/pipeline/proc/temporarily_store_air()
//Update individual gas_mixtures by volume ratio
for(var/obj/machinery/atmospherics/pipe/member in members)
member.air_temporary = new
member.air_temporary.volume = member.volume
member.air_temporary.copy_from(air)
var/member_gases = member.air_temporary.gases
for(var/id in member_gases)
member_gases[id][MOLES] *= member.volume/air.volume
member.air_temporary.temperature = air.temperature
/datum/pipeline/proc/temperature_interact(turf/target, share_volume, thermal_conductivity)
var/total_heat_capacity = air.heat_capacity()
var/partial_heat_capacity = total_heat_capacity*(share_volume/air.volume)
var/target_temperature
var/target_heat_capacity
if(istype(target, /turf/open))
var/turf/open/modeled_location = target
target_temperature = modeled_location.GetTemperature()
target_heat_capacity = modeled_location.GetHeatCapacity()
if(modeled_location.blocks_air)
if((modeled_location.heat_capacity>0) && (partial_heat_capacity>0))
var/delta_temperature = air.temperature - target_temperature
var/heat = thermal_conductivity*delta_temperature* \
(partial_heat_capacity*target_heat_capacity/(partial_heat_capacity+target_heat_capacity))
air.temperature -= heat/total_heat_capacity
modeled_location.TakeTemperature(heat/target_heat_capacity)
else
var/delta_temperature = 0
var/sharer_heat_capacity = 0
delta_temperature = (air.temperature - target_temperature)
sharer_heat_capacity = target_heat_capacity
var/self_temperature_delta = 0
var/sharer_temperature_delta = 0
if((sharer_heat_capacity>0) && (partial_heat_capacity>0))
var/heat = thermal_conductivity*delta_temperature* \
(partial_heat_capacity*sharer_heat_capacity/(partial_heat_capacity+sharer_heat_capacity))
self_temperature_delta = -heat/total_heat_capacity
sharer_temperature_delta = heat/sharer_heat_capacity
else
return 1
air.temperature += self_temperature_delta
modeled_location.TakeTemperature(sharer_temperature_delta)
else
if((target.heat_capacity>0) && (partial_heat_capacity>0))
var/delta_temperature = air.temperature - target.temperature
var/heat = thermal_conductivity*delta_temperature* \
(partial_heat_capacity*target.heat_capacity/(partial_heat_capacity+target.heat_capacity))
air.temperature -= heat/total_heat_capacity
update = 1
/datum/pipeline/proc/reconcile_air()
var/list/datum/gas_mixture/GL = list()
var/list/datum/pipeline/PL = list()
PL += src
for(var/i = 1; i <= PL.len; i++) //can't do a for-each here because we may add to the list within the loop
var/datum/pipeline/P = PL[i]
GL += P.air
GL += P.other_airs
for(var/obj/machinery/atmospherics/components/binary/valve/V in P.other_atmosmch)
if(V.open)
PL |= V.PARENT1
PL |= V.PARENT2
for(var/obj/machinery/atmospherics/components/unary/portables_connector/C in P.other_atmosmch)
if(C.connected_device)
GL += C.portableConnectorReturnAir()
var/total_thermal_energy = 0
var/total_heat_capacity = 0
var/datum/gas_mixture/total_gas_mixture = new(0)
for(var/i in GL)
var/datum/gas_mixture/G = i
total_gas_mixture.volume += G.volume
total_gas_mixture.merge(G)
total_thermal_energy += G.thermal_energy()
total_heat_capacity += G.heat_capacity()
total_gas_mixture.temperature = total_heat_capacity ? total_thermal_energy/total_heat_capacity : 0
if(total_gas_mixture.volume > 0)
//Update individual gas_mixtures by volume ratio
for(var/i in GL)
var/datum/gas_mixture/G = i
G.copy_from(total_gas_mixture)
var/list/G_gases = G.gases
for(var/id in G_gases)
G_gases[id][MOLES] *= G.volume/total_gas_mixture.volume
@@ -0,0 +1,140 @@
/obj/machinery/meter
name = "gas flow meter"
desc = "It measures something."
icon = 'icons/obj/meter.dmi'
icon_state = "meterX"
var/obj/machinery/atmospherics/pipe/target = null
anchored = 1
power_channel = ENVIRON
var/frequency = 0
var/id_tag
use_power = 1
idle_power_usage = 2
active_power_usage = 4
/obj/machinery/meter/New()
..()
SSair.atmos_machinery += src
src.target = locate(/obj/machinery/atmospherics/pipe) in loc
return 1
/obj/machinery/meter/Destroy()
SSair.atmos_machinery -= src
src.target = null
return ..()
/obj/machinery/meter/initialize()
if (!target)
src.target = locate(/obj/machinery/atmospherics/pipe) in loc
/obj/machinery/meter/process_atmos()
if(!target)
icon_state = "meterX"
return 0
if(stat & (BROKEN|NOPOWER))
icon_state = "meter0"
return 0
use_power(5)
var/datum/gas_mixture/environment = target.return_air()
if(!environment)
icon_state = "meterX"
return 0
var/env_pressure = environment.return_pressure()
if(env_pressure <= 0.15*ONE_ATMOSPHERE)
icon_state = "meter0"
else if(env_pressure <= 1.8*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*0.3) + 0.5)
icon_state = "meter1_[val]"
else if(env_pressure <= 30*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*5)-0.35) + 1
icon_state = "meter2_[val]"
else if(env_pressure <= 59*ONE_ATMOSPHERE)
var/val = round(env_pressure/(ONE_ATMOSPHERE*5) - 6) + 1
icon_state = "meter3_[val]"
else
icon_state = "meter4"
if(frequency)
var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency)
if(!radio_connection)
return
var/datum/signal/signal = new
signal.source = src
signal.transmission_method = 1
signal.data = list(
"id_tag" = id_tag,
"device" = "AM",
"pressure" = round(env_pressure),
"sigtype" = "status"
)
radio_connection.post_signal(src, signal)
/obj/machinery/meter/proc/status()
var/t = ""
if (src.target)
var/datum/gas_mixture/environment = target.return_air()
if(environment)
t += "The pressure gauge reads [round(environment.return_pressure(), 0.01)] kPa; [round(environment.temperature,0.01)] K ([round(environment.temperature-T0C,0.01)]&deg;C)"
else
t += "The sensor error light is blinking."
else
t += "The connect error light is blinking."
return t
/obj/machinery/meter/examine(mob/user)
..()
user << status()
/obj/machinery/meter/attackby(obj/item/weapon/W, mob/user, params)
if (istype(W, /obj/item/weapon/wrench))
playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
user << "<span class='notice'>You begin to unfasten \the [src]...</span>"
if (do_after(user, 40/W.toolspeed, target = src))
user.visible_message( \
"[user] unfastens \the [src].", \
"<span class='notice'>You unfasten \the [src].</span>", \
"<span class='italics'>You hear ratchet.</span>")
new /obj/item/pipe_meter(src.loc)
qdel(src)
else
return ..()
/obj/machinery/meter/attack_ai(mob/user)
return src.attack_hand(user)
/obj/machinery/meter/attack_paw(mob/user)
return src.attack_hand(user)
/obj/machinery/meter/attack_hand(mob/user)
if(stat & (NOPOWER|BROKEN))
return 1
else
usr << status()
return 1
/obj/machinery/meter/singularity_pull(S, current_size)
if(current_size >= STAGE_FIVE)
new /obj/item/pipe_meter(loc)
qdel(src)
// TURF METER - REPORTS A TILE'S AIR CONTENTS
// why are you yelling?
/obj/machinery/meter/turf/New()
..()
src.target = loc
return 1
/obj/machinery/meter/turf/initialize()
if (!target)
src.target = loc
@@ -0,0 +1,35 @@
/obj/machinery/zvent
name = "interfloor air transfer system"
icon = 'icons/obj/atmospherics/components/unary_devices.dmi'
icon_state = "vent_map"
density = 0
anchored=1
var/on = 0
var/volume_rate = 800
/obj/machinery/zvent/New()
..()
SSair.atmos_machinery += src
/obj/machinery/zvent/Destroy()
SSair.atmos_machinery -= src
return ..()
/obj/machinery/zvent/process_atmos()
//all this object does, is make its turf share air with the ones above and below it, if they have a vent too.
if (istype(loc,/turf)) //if we're not on a valid turf, forget it
for (var/new_z in list(-1,1)) //change this list if a fancier system of z-levels gets implemented
var/turf/open/zturf_conn = locate(x,y,z+new_z)
if (istype(zturf_conn))
var/obj/machinery/zvent/zvent_conn= locate(/obj/machinery/zvent) in zturf_conn
if (istype(zvent_conn))
//both floors have simulated turfs, share()
var/turf/open/myturf = loc
var/datum/gas_mixture/conn_air = zturf_conn.air //TODO: pop culture reference
var/datum/gas_mixture/my_air = myturf.air
if (istype(conn_air) && istype(my_air))
my_air.share(conn_air)
air_update_turf()
@@ -0,0 +1,90 @@
/obj/machinery/atmospherics/pipe/heat_exchanging/
icon = 'icons/obj/atmospherics/pipes/heat.dmi'
level = 2
var/initialize_directions_he
var/minimum_temperature_difference = 20
var/thermal_conductivity = WINDOW_HEAT_TRANSFER_COEFFICIENT
color = "#404040"
buckle_lying = 1
var/icon_temperature = T20C //stop small changes in temperature causing icon refresh
burn_state = LAVA_PROOF
/obj/machinery/atmospherics/pipe/heat_exchanging/New()
..()
color = "#404040"
/obj/machinery/atmospherics/pipe/heat_exchanging/can_be_node(obj/machinery/atmospherics/pipe/heat_exchanging/target)
if(!istype(target))
return 0
if(target.initialize_directions_he & get_dir(target,src))
return 1
/obj/machinery/atmospherics/pipe/heat_exchanging/hide()
return
/obj/machinery/atmospherics/pipe/heat_exchanging/GetInitDirections()
return ..() | initialize_directions_he
/obj/machinery/atmospherics/pipe/heat_exchanging/process_atmos()
var/environment_temperature = 0
var/datum/gas_mixture/pipe_air = return_air()
var/turf/T = loc
if(istype(T))
if(istype(T, /turf/open/floor/plating/lava))
environment_temperature = 5000
else if(T.blocks_air)
environment_temperature = T.temperature
else
var/turf/open/OT = T
environment_temperature = OT.GetTemperature()
else
environment_temperature = T.temperature
if(abs(environment_temperature-pipe_air.temperature) > minimum_temperature_difference)
parent.temperature_interact(T, volume, thermal_conductivity)
//heatup/cooldown any mobs buckled to ourselves based on our temperature
if(has_buckled_mobs())
var/hc = pipe_air.heat_capacity()
var/mob/living/heat_source = buckled_mobs[1]
//Best guess-estimate of the total bodytemperature of all the mobs, since they share the same environment it's ~ok~ to guess like this
var/avg_temp = (pipe_air.temperature * hc + (heat_source.bodytemperature * buckled_mobs.len) * 3500) / (hc + (buckled_mobs.len * 3500))
for(var/m in buckled_mobs)
var/mob/living/L = m
L.bodytemperature = avg_temp
pipe_air.temperature = avg_temp
/obj/machinery/atmospherics/pipe/heat_exchanging/process()
if(!parent)
return //machines subsystem fires before atmos is initialized so this prevents race condition runtimes
var/datum/gas_mixture/pipe_air = return_air()
//Heat causes pipe to glow
if(pipe_air.temperature && (icon_temperature > 500 || pipe_air.temperature > 500)) //glow starts at 500K
if(abs(pipe_air.temperature - icon_temperature) > 10)
icon_temperature = pipe_air.temperature
var/h_r = heat2colour_r(icon_temperature)
var/h_g = heat2colour_g(icon_temperature)
var/h_b = heat2colour_b(icon_temperature)
if(icon_temperature < 2000)//scale glow until 2000K
var/scale = (icon_temperature - 500) / 1500
h_r = 64 + (h_r - 64) * scale
h_g = 64 + (h_g - 64) * scale
h_b = 64 + (h_b - 64) * scale
animate(src, color = rgb(h_r, h_g, h_b), time = 20, easing = SINE_EASING)
//burn any mobs buckled based on temperature
if(has_buckled_mobs())
var/heat_limit = 1000
if(pipe_air.temperature > heat_limit + 1)
for(var/m in buckled_mobs)
var/mob/living/buckled_mob = m
buckled_mob.apply_damage(4 * log(pipe_air.temperature - heat_limit), BURN, "chest")
@@ -0,0 +1,50 @@
/obj/machinery/atmospherics/pipe/heat_exchanging/junction
icon = 'icons/obj/atmospherics/pipes/junction.dmi'
icon_state = "intact"
name = "junction"
desc = "A one meter junction that connects regular and heat-exchanging pipe"
minimum_temperature_difference = 300
thermal_conductivity = WALL_HEAT_TRANSFER_COEFFICIENT
dir = SOUTH
initialize_directions = NORTH
initialize_directions_he = SOUTH
device_type = BINARY
/obj/machinery/atmospherics/pipe/heat_exchanging/junction/SetInitDirections()
switch(dir)
if(SOUTH)
initialize_directions = NORTH
initialize_directions_he = SOUTH
if(NORTH)
initialize_directions = SOUTH
initialize_directions_he = NORTH
if(EAST)
initialize_directions = WEST
initialize_directions_he = EAST
if(WEST)
initialize_directions = EAST
initialize_directions_he = WEST
/obj/machinery/atmospherics/pipe/heat_exchanging/junction/atmosinit()
var/node2_connect = dir
var/node1_connect = turn(dir, 180)
var/list/node_connects = list(node1_connect, node2_connect)
..(node_connects)
/obj/machinery/atmospherics/pipe/heat_exchanging/junction/can_be_node(obj/machinery/atmospherics/target, iteration)
var/init_dir
switch(iteration)
if(1)
init_dir = target.initialize_directions
if(2)
var/obj/machinery/atmospherics/pipe/heat_exchanging/H = target
if(!istype(H))
return 0
init_dir = H.initialize_directions_he
if(init_dir & get_dir(target,src))
return 1
@@ -0,0 +1,60 @@
//3-way manifold
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold
icon_state = "manifold"
name = "pipe manifold"
desc = "A manifold composed of regular pipes"
dir = SOUTH
initialize_directions_he = EAST|NORTH|WEST
device_type = TRINARY
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions_he = EAST|SOUTH|WEST
if(SOUTH)
initialize_directions_he = WEST|NORTH|EAST
if(EAST)
initialize_directions_he = SOUTH|WEST|NORTH
if(WEST)
initialize_directions_he = NORTH|EAST|SOUTH
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold/update_icon()
var/invis = invisibility ? "-f" : ""
icon_state = "manifold_center[invis]"
cut_overlays()
//Add non-broken pieces
for(DEVICE_TYPE_LOOP)
if(NODE_I)
add_overlay(getpipeimage('icons/obj/atmospherics/pipes/heat.dmi', "manifold_intact[invis]", get_dir(src, NODE_I)))
//4-way manifold
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w
icon_state = "manifold4w"
name = "4-way pipe manifold"
desc = "A manifold composed of heat-exchanging pipes"
initialize_directions_he = NORTH|SOUTH|EAST|WEST
device_type = QUATERNARY
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w/SetInitDirections()
initialize_directions_he = initial(initialize_directions_he)
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w/update_icon()
var/invis = invisibility ? "-f" : ""
icon_state = "manifold4w_center[invis]"
cut_overlays()
//Add non-broken pieces
for(DEVICE_TYPE_LOOP)
if(NODE_I)
add_overlay(getpipeimage('icons/obj/atmospherics/pipes/heat.dmi', "manifold_intact[invis]", get_dir(src, NODE_I)))
@@ -0,0 +1,33 @@
/obj/machinery/atmospherics/pipe/heat_exchanging/simple
icon_state = "intact"
name = "pipe"
desc = "A one meter section of heat-exchanging pipe"
dir = SOUTH
initialize_directions_he = SOUTH|NORTH
device_type = BINARY
/obj/machinery/atmospherics/pipe/heat_exchanging/simple/SetInitDirections()
if(dir in diagonals)
initialize_directions_he = dir
switch(dir)
if(NORTH,SOUTH)
initialize_directions_he = SOUTH|NORTH
if(EAST,WEST)
initialize_directions_he = WEST|EAST
/obj/machinery/atmospherics/pipe/heat_exchanging/simple/proc/normalize_dir()
if(dir==SOUTH)
setDir(NORTH)
else if(dir==WEST)
setDir(EAST)
/obj/machinery/atmospherics/pipe/heat_exchanging/simple/atmosinit()
normalize_dir()
..()
/obj/machinery/atmospherics/pipe/heat_exchanging/simple/update_icon()
normalize_dir()
..()
@@ -0,0 +1,110 @@
/*
3-Way Manifold
*/
/obj/machinery/atmospherics/pipe/manifold
icon = 'icons/obj/atmospherics/pipes/manifold.dmi'
icon_state = "manifold"
name = "pipe manifold"
desc = "A manifold composed of regular pipes"
dir = SOUTH
initialize_directions = EAST|NORTH|WEST
device_type = TRINARY
/obj/machinery/atmospherics/pipe/manifold/SetInitDirections()
switch(dir)
if(NORTH)
initialize_directions = EAST|SOUTH|WEST
if(SOUTH)
initialize_directions = WEST|NORTH|EAST
if(EAST)
initialize_directions = SOUTH|WEST|NORTH
if(WEST)
initialize_directions = NORTH|EAST|SOUTH
/obj/machinery/atmospherics/pipe/manifold/update_icon()
var/invis = invisibility ? "-f" : ""
icon_state = "manifold_center[invis]"
cut_overlays()
//Add non-broken pieces
for(DEVICE_TYPE_LOOP)
if(NODE_I)
add_overlay(getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full[invis]", get_dir(src, NODE_I)))
//Colored pipes, use these for mapping
/obj/machinery/atmospherics/pipe/manifold/general
name="pipe"
/obj/machinery/atmospherics/pipe/manifold/general/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold/general/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold/scrubbers
name="scrubbers pipe"
pipe_color=rgb(255,0,0)
color=rgb(255,0,0)
/obj/machinery/atmospherics/pipe/manifold/scrubbers/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold/supply
name="air supply pipe"
pipe_color=rgb(0,0,255)
color=rgb(0,0,255)
/obj/machinery/atmospherics/pipe/manifold/supply/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold/supply/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold/supplymain
name="main air supply pipe"
pipe_color=rgb(130,43,272)
color=rgb(130,43,272)
/obj/machinery/atmospherics/pipe/manifold/supplymain/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold/supplymain/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold/yellow
pipe_color=rgb(255,198,0)
color=rgb(255,198,0)
/obj/machinery/atmospherics/pipe/manifold/yellow/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold/yellow/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold/cyan
pipe_color=rgb(0,256,249)
color=rgb(0,256,249)
/obj/machinery/atmospherics/pipe/manifold/cyan/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold/cyan/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold/green
pipe_color=rgb(30,256,0)
color=rgb(30,256,0)
/obj/machinery/atmospherics/pipe/manifold/green/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold/green/hidden
level = 1
@@ -0,0 +1,101 @@
/*
4-way manifold
*/
/obj/machinery/atmospherics/pipe/manifold4w
icon = 'icons/obj/atmospherics/pipes/manifold.dmi'
icon_state = "manifold4w"
name = "4-way pipe manifold"
desc = "A manifold composed of regular pipes"
initialize_directions = NORTH|SOUTH|EAST|WEST
device_type = QUATERNARY
/obj/machinery/atmospherics/pipe/manifold4w/SetInitDirections()
initialize_directions = initial(initialize_directions)
/obj/machinery/atmospherics/pipe/manifold4w/update_icon()
var/invis = invisibility ? "-f" : ""
icon_state = "manifold4w_center[invis]"
cut_overlays()
//Add non-broken pieces
for(DEVICE_TYPE_LOOP)
if(NODE_I)
add_overlay(getpipeimage('icons/obj/atmospherics/pipes/manifold.dmi', "manifold_full[invis]", get_dir(src, NODE_I)))
//Colored pipes, use these for mapping
/obj/machinery/atmospherics/pipe/manifold4w/general
name="pipe"
/obj/machinery/atmospherics/pipe/manifold4w/general/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold4w/general/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold4w/scrubbers
name="scrubbers pipe"
pipe_color=rgb(255,0,0)
color=rgb(255,0,0)
/obj/machinery/atmospherics/pipe/manifold4w/scrubbers/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold4w/scrubbers/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold4w/supply
name="air supply pipe"
pipe_color=rgb(0,0,255)
color=rgb(0,0,255)
/obj/machinery/atmospherics/pipe/manifold4w/supply/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold4w/supply/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold4w/supplymain
name="main air supply pipe"
pipe_color=rgb(130,43,272)
color=rgb(130,43,272)
/obj/machinery/atmospherics/pipe/manifold4w/supplymain/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold4w/supplymain/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold4w/yellow
pipe_color=rgb(255,198,0)
color=rgb(255,198,0)
/obj/machinery/atmospherics/pipe/manifold4w/yellow/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold4w/yellow/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold4w/cyan
pipe_color=rgb(0,256,249)
color=rgb(0,256,249)
/obj/machinery/atmospherics/pipe/manifold4w/cyan/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold4w/cyan/hidden
level = 1
/obj/machinery/atmospherics/pipe/manifold4w/green
pipe_color=rgb(30,256,0)
color=rgb(30,256,0)
/obj/machinery/atmospherics/pipe/manifold4w/green/visible
level = 2
/obj/machinery/atmospherics/pipe/manifold4w/green/hidden
level = 1
@@ -0,0 +1,100 @@
/obj/machinery/atmospherics/pipe
var/datum/gas_mixture/air_temporary //used when reconstructing a pipeline that broke
var/volume = 0
level = 1
use_power = 0
can_unwrench = 1
var/datum/pipeline/parent = null
//Buckling
can_buckle = 1
buckle_requires_restraints = 1
buckle_lying = -1
/obj/machinery/atmospherics/pipe/New()
color = pipe_color
volume = 35 * device_type
..()
/obj/machinery/atmospherics/pipe/nullifyNode(I)
var/obj/machinery/atmospherics/oldN = NODE_I
..()
if(oldN)
oldN.build_network()
/obj/machinery/atmospherics/pipe/update_icon() //overridden by manifolds
if(NODE1&&NODE2)
icon_state = "intact[invisibility ? "-f" : "" ]"
else
var/have_node1 = NODE1?1:0
var/have_node2 = NODE2?1:0
icon_state = "exposed[have_node1][have_node2][invisibility ? "-f" : "" ]"
/obj/machinery/atmospherics/pipe/atmosinit()
var/turf/T = loc // hide if turf is not intact
hide(T.intact)
..()
/obj/machinery/atmospherics/pipe/hide(i)
if(level == 1 && istype(loc, /turf))
invisibility = i ? INVISIBILITY_MAXIMUM : 0
update_icon()
/obj/machinery/atmospherics/pipe/proc/check_pressure(pressure)
//Return 1 if parent should continue checking other pipes
//Return null if parent should stop checking other pipes. Recall: del(src) will by default return null
return 1
/obj/machinery/atmospherics/pipe/proc/releaseAirToTurf()
if(air_temporary)
var/turf/T = loc
T.assume_air(air_temporary)
air_update_turf()
/obj/machinery/atmospherics/pipe/return_air()
return parent.air
/obj/machinery/atmospherics/pipe/build_network()
if(!parent)
parent = new /datum/pipeline()
parent.build_pipeline(src)
/obj/machinery/atmospherics/pipe/attackby(obj/item/weapon/W, mob/user, params)
if(istype(W, /obj/item/device/analyzer))
atmosanalyzer_scan(parent.air, user)
else
return ..()
/obj/machinery/atmospherics/pipe/returnPipenet()
return parent
/obj/machinery/atmospherics/pipe/setPipenet(datum/pipeline/P)
parent = P
/obj/machinery/atmospherics/pipe/Destroy()
releaseAirToTurf()
qdel(air_temporary)
air_temporary = null
var/turf/T = loc
for(var/obj/machinery/meter/meter in T)
if(meter.target == src)
var/obj/item/pipe_meter/PM = new (T)
meter.transfer_fingerprints_to(PM)
qdel(meter)
. = ..()
if(parent && !qdeleted(parent))
qdel(parent)
parent = null
/obj/machinery/atmospherics/pipe/proc/update_node_icon()
for(DEVICE_TYPE_LOOP)
if(NODE_I)
var/obj/machinery/atmospherics/N = NODE_I
N.update_icon()
/obj/machinery/atmospherics/pipe/returnPipenets()
. = list(parent)
@@ -0,0 +1,112 @@
/*
Simple Pipe
The regular pipe you see everywhere, including bent ones.
*/
/obj/machinery/atmospherics/pipe/simple
icon = 'icons/obj/atmospherics/pipes/simple.dmi'
icon_state = "intact"
name = "pipe"
desc = "A one meter section of regular pipe"
dir = SOUTH
initialize_directions = SOUTH|NORTH
device_type = BINARY
/obj/machinery/atmospherics/pipe/simple/SetInitDirections()
if(dir in diagonals)
initialize_directions = dir
switch(dir)
if(NORTH,SOUTH)
initialize_directions = SOUTH|NORTH
if(EAST,WEST)
initialize_directions = EAST|WEST
/obj/machinery/atmospherics/pipe/simple/atmosinit()
normalize_dir()
..()
/obj/machinery/atmospherics/pipe/simple/proc/normalize_dir()
if(dir==SOUTH)
setDir(NORTH)
else if(dir==WEST)
setDir(EAST)
/obj/machinery/atmospherics/pipe/simple/update_icon()
normalize_dir()
..()
//Colored pipes, use these for mapping
/obj/machinery/atmospherics/pipe/simple/general
name="pipe"
/obj/machinery/atmospherics/pipe/simple/general/visible
level = 2
/obj/machinery/atmospherics/pipe/simple/general/hidden
level = 1
/obj/machinery/atmospherics/pipe/simple/scrubbers
name="scrubbers pipe"
pipe_color=rgb(255,0,0)
color=rgb(255,0,0)
/obj/machinery/atmospherics/pipe/simple/scrubbers/visible
level = 2
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden
level = 1
/obj/machinery/atmospherics/pipe/simple/supply
name="air supply pipe"
pipe_color=rgb(0,0,255)
color=rgb(0,0,255)
/obj/machinery/atmospherics/pipe/simple/supply/visible
level = 2
/obj/machinery/atmospherics/pipe/simple/supply/hidden
level = 1
/obj/machinery/atmospherics/pipe/simple/supplymain
name="main air supply pipe"
pipe_color=rgb(130,43,272)
color=rgb(130,43,272)
/obj/machinery/atmospherics/pipe/simple/supplymain/visible
level = 2
/obj/machinery/atmospherics/pipe/simple/supplymain/hidden
level = 1
/obj/machinery/atmospherics/pipe/simple/yellow
pipe_color=rgb(255,198,0)
color=rgb(255,198,0)
/obj/machinery/atmospherics/pipe/simple/yellow/visible
level = 2
/obj/machinery/atmospherics/pipe/simple/yellow/hidden
level = 1
/obj/machinery/atmospherics/pipe/simple/cyan
pipe_color=rgb(0,256,249)
color=rgb(0,256,249)
/obj/machinery/atmospherics/pipe/simple/cyan/visible
level = 2
/obj/machinery/atmospherics/pipe/simple/cyan/hidden
level = 1
/obj/machinery/atmospherics/pipe/simple/green
pipe_color=rgb(30,256,0)
color=rgb(30,256,0)
/obj/machinery/atmospherics/pipe/simple/green/visible
level = 2
/obj/machinery/atmospherics/pipe/simple/green/hidden
level = 1
@@ -0,0 +1,326 @@
#define CAN_MAX_RELEASE_PRESSURE (ONE_ATMOSPHERE * 10)
#define CAN_MIN_RELEASE_PRESSURE (ONE_ATMOSPHERE / 10)
#define CAN_DEFAULT_RELEASE_PRESSURE (ONE_ATMOSPHERE)
/obj/machinery/portable_atmospherics/canister
name = "canister"
desc = "A canister for the storage of gas."
icon_state = "yellow"
density = 1
var/valve_open = FALSE
var/obj/machinery/atmospherics/components/binary/passive_gate/pump
var/release_log = ""
volume = 1000
var/filled = 0.5
var/gas_type = ""
var/release_pressure = ONE_ATMOSPHERE
var/health = 100
pressure_resistance = 7 * ONE_ATMOSPHERE
var/temperature_resistance = 1000 + T0C
var/update = 0
var/static/list/label2types = list(
"n2" = /obj/machinery/portable_atmospherics/canister/nitrogen,
"o2" = /obj/machinery/portable_atmospherics/canister/oxygen,
"co2" = /obj/machinery/portable_atmospherics/canister/carbon_dioxide,
"plasma" = /obj/machinery/portable_atmospherics/canister/toxins,
"n2o" = /obj/machinery/portable_atmospherics/canister/nitrous_oxide,
"bz" = /obj/machinery/portable_atmospherics/canister/bz,
"air" = /obj/machinery/portable_atmospherics/canister/air,
"caution" = /obj/machinery/portable_atmospherics/canister,
)
/obj/machinery/portable_atmospherics/canister/nitrogen
name = "n2 canister"
desc = "Nitrogen gas. Reportedly useful for something."
icon_state = "red"
gas_type = "n2"
/obj/machinery/portable_atmospherics/canister/oxygen
name = "o2 canister"
desc = "Oxygen. Necessary for human life."
icon_state = "blue"
gas_type = "o2"
/obj/machinery/portable_atmospherics/canister/carbon_dioxide
name = "co2 canister"
desc = "Carbon dioxide. What the fuck is carbon dioxide?"
icon_state = "black"
gas_type = "co2"
/obj/machinery/portable_atmospherics/canister/toxins
name = "plasma canister"
desc = "Plasma gas. The reason YOU are here. Highly toxic."
icon_state = "orange"
gas_type = "plasma"
/obj/machinery/portable_atmospherics/canister/agent_b
name = "agent b canister"
desc = "Oxygen Agent B. You're not quite sure what it does."
gas_type = "agent_b"
/obj/machinery/portable_atmospherics/canister/bz
name = "BZ canister"
desc = "BZ, a powerful hallucinogenic nerve agent."
icon_state = "purple"
gas_type = "bz"
/obj/machinery/portable_atmospherics/canister/nitrous_oxide
name = "n2o canister"
desc = "Nitrous oxide gas. Known to cause drowsiness."
icon_state = "redws"
gas_type = "n2o"
/obj/machinery/portable_atmospherics/canister/air
name = "air canister"
desc = "Pre-mixed air."
icon_state = "grey"
/obj/machinery/portable_atmospherics/canister/New(loc, datum/gas_mixture/existing_mixture)
..()
if(existing_mixture)
air_contents.copy_from(existing_mixture)
else
create_gas()
pump = new(src, FALSE)
pump.on = TRUE
pump.stat = 0
pump.build_network()
update_icon()
/obj/machinery/portable_atmospherics/canister/Destroy()
qdel(pump)
pump = null
return ..()
/obj/machinery/portable_atmospherics/canister/proc/create_gas()
if(gas_type)
air_contents.add_gas(gas_type)
air_contents.gases[gas_type][MOLES] = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
/obj/machinery/portable_atmospherics/canister/air/create_gas()
air_contents.add_gases("o2","n2")
air_contents.gases["o2"][MOLES] = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
air_contents.gases["n2"][MOLES] = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
#define HOLDING 1
#define CONNECTED 2
#define EMPTY 4
#define LOW 8
#define FULL 16
#define DANGER 32
/obj/machinery/portable_atmospherics/canister/update_icon()
if(stat & BROKEN)
cut_overlays()
icon_state = "[initial(icon_state)]-1"
return
var/last_update = update
update = 0
if(holding)
update |= HOLDING
if(connected_port)
update |= CONNECTED
var/pressure = air_contents.return_pressure()
if(pressure < 10)
update |= EMPTY
else if(pressure < ONE_ATMOSPHERE)
update |= LOW
else if(pressure < 15 * ONE_ATMOSPHERE)
update |= FULL
else
update |= DANGER
if(update == last_update)
return
cut_overlays()
if(update & HOLDING)
add_overlay("can-open")
if(update & CONNECTED)
add_overlay("can-connector")
if(update & EMPTY)
add_overlay("can-o0")
else if(update & LOW)
add_overlay("can-o1")
else if(update & FULL)
add_overlay("can-o2")
else if(update & DANGER)
add_overlay("can-o3")
#undef HOLDING
#undef CONNECTED
#undef EMPTY
#undef LOW
#undef FULL
#undef DANGER
/obj/machinery/portable_atmospherics/canister/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(exposed_temperature > temperature_resistance)
take_damage(5, BRUTE, 0)
/obj/machinery/portable_atmospherics/canister/take_damage(damage, damage_type = BRUTE, sound_effect = 1)
switch(damage_type)
if(BRUTE)
if(sound_effect)
if(damage)
playsound(loc, 'sound/weapons/smash.ogg', 50, 1)
else
playsound(loc, 'sound/weapons/tap.ogg', 50, 1)
if(BURN)
if(sound_effect)
playsound(src.loc, 'sound/items/Welder.ogg', 100, 1)
else
return
if(stat & BROKEN)
return
health = max( health - damage, 0)
if(!health)
disconnect()
var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles())
var/turf/T = get_turf(src)
T.assume_air(expelled_gas)
air_update_turf()
stat |= BROKEN
density = 0
playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3)
update_icon()
investigate_log("was destroyed.", "atmos")
if(holding)
holding.loc = T
holding = null
/obj/machinery/portable_atmospherics/canister/process_atmos()
..()
if(stat & BROKEN)
return PROCESS_KILL
if(!valve_open)
pump.AIR1 = null
pump.AIR2 = null
return
var/turf/T = get_turf(src)
pump.AIR1 = air_contents
pump.AIR2 = holding ? holding.air_contents : T.return_air()
pump.target_pressure = release_pressure
pump.process_atmos() // Pump gas.
if(!holding)
air_update_turf() // Update the environment if needed.
update_icon()
/obj/machinery/portable_atmospherics/canister/blob_act(obj/effect/blob/B)
take_damage(100, BRUTE, 0)
/obj/machinery/portable_atmospherics/canister/burn()
take_damage(health, BURN, 1)
..()
/obj/machinery/portable_atmospherics/canister/bullet_act(obj/item/projectile/P)
. = ..()
take_damage(P.damage / 2, P.damage_type, 0)
/obj/machinery/portable_atmospherics/canister/ex_act(severity, target)
switch(severity)
if(1)
if((stat & BROKEN) || prob(30))
qdel(src)
else
take_damage(100, BRUTE, 0)
if(2)
if(stat & BROKEN)
qdel(src)
else
take_damage(rand(40, 110), BRUTE, 0)
if(3)
take_damage(rand(15, 40), BRUTE, 0)
/obj/machinery/portable_atmospherics/canister/attacked_by(obj/item/I, mob/user)
if(I.force)
user.visible_message("<span class='danger'>[user] has hit [src] with [I]!</span>", "<span class='danger'>You hit [src] with [I]!</span>")
investigate_log("was smacked with \a [I] by [key_name(user)].", "atmos")
add_fingerprint(user)
take_damage(I.force, I.damtype, 1)
/obj/machinery/portable_atmospherics/canister/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = physical_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "canister", name, 420, 405, master_ui, state)
ui.open()
/obj/machinery/portable_atmospherics/canister/ui_data()
var/data = list()
data["portConnected"] = connected_port ? 1 : 0
data["tankPressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["releasePressure"] = round(release_pressure ? release_pressure : 0)
data["defaultReleasePressure"] = round(CAN_DEFAULT_RELEASE_PRESSURE)
data["minReleasePressure"] = round(CAN_MIN_RELEASE_PRESSURE)
data["maxReleasePressure"] = round(CAN_MAX_RELEASE_PRESSURE)
data["valveOpen"] = valve_open ? 1 : 0
data["hasHoldingTank"] = holding ? 1 : 0
if (holding)
data["holdingTank"] = list()
data["holdingTank"]["name"] = holding.name
data["holdingTank"]["tankPressure"] = round(holding.air_contents.return_pressure())
return data
/obj/machinery/portable_atmospherics/canister/ui_act(action, params)
if(..())
return
switch(action)
if("relabel")
var/label = input("New canister label:", name) as null|anything in label2types
if(label && !..())
var/newtype = label2types[label]
if(newtype)
var/obj/machinery/portable_atmospherics/canister/replacement = new newtype(loc, air_contents)
replacement.interact(usr)
qdel(src)
if("pressure")
var/pressure = params["pressure"]
if(pressure == "reset")
pressure = CAN_DEFAULT_RELEASE_PRESSURE
. = TRUE
else if(pressure == "min")
pressure = CAN_MIN_RELEASE_PRESSURE
. = TRUE
else if(pressure == "max")
pressure = CAN_MAX_RELEASE_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New release pressure ([CAN_MIN_RELEASE_PRESSURE]-[CAN_MAX_RELEASE_PRESSURE] kPa):", name, release_pressure) as num|null
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
release_pressure = Clamp(round(pressure), CAN_MIN_RELEASE_PRESSURE, CAN_MAX_RELEASE_PRESSURE)
investigate_log("was set to [release_pressure] kPa by [key_name(usr)].", "atmos")
if("valve")
var/logmsg
valve_open = !valve_open
if(valve_open)
logmsg = "Valve was <b>opened</b> by [key_name(usr)], starting a transfer into \the [holding || "air"].<br>"
if(!holding)
var/plasma = air_contents.gases["plasma"]
var/n2o = air_contents.gases["n2o"]
var/bz = air_contents.gases["bz"]
if(n2o || plasma || bz)
message_admins("[key_name_admin(usr)] (<A HREF='?_src_=holder;adminmoreinfo=\ref[usr]'>?</A>) (<A HREF='?_src_=holder;adminplayerobservefollow=\ref[usr]'>FLW</A>) opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""][(n2o || plasma) && bz ? " & " : ""][bz ? "BZ" : ""]! (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[x];Y=[y];Z=[z]'>JMP</a>)")
log_admin("[key_name(usr)] opened a canister that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""][(n2o || plasma) && bz ? " & " : ""][bz ? "BZ" : ""] at [x], [y], [z]")
else
logmsg = "Valve was <b>closed</b> by [key_name(usr)], stopping the transfer into \the [holding || "air"].<br>"
investigate_log(logmsg, "atmos")
release_log += logmsg
. = TRUE
if("eject")
if(holding)
if(valve_open)
investigate_log("[key_name(usr)] removed the [holding], leaving the valve open and transfering into the <span class='boldannounce'>air</span><br>", "atmos")
holding.loc = get_turf(src)
holding = null
. = TRUE
update_icon()
@@ -0,0 +1,108 @@
/obj/machinery/portable_atmospherics
name = "portable_atmospherics"
icon = 'icons/obj/atmos.dmi'
use_power = 0
var/datum/gas_mixture/air_contents
var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port
var/obj/item/weapon/tank/holding
var/volume = 0
var/maximum_pressure = 90 * ONE_ATMOSPHERE
/obj/machinery/portable_atmospherics/New()
..()
SSair.atmos_machinery += src
air_contents = new
air_contents.volume = volume
air_contents.temperature = T20C
return 1
/obj/machinery/portable_atmospherics/Destroy()
SSair.atmos_machinery -= src
disconnect()
qdel(air_contents)
air_contents = null
return ..()
/obj/machinery/portable_atmospherics/process_atmos()
if(!connected_port) // Pipe network handles reactions if connected.
air_contents.react()
else
update_icon()
/obj/machinery/portable_atmospherics/return_air()
return air_contents
/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port)
//Make sure not already connected to something else
if(connected_port || !new_port || new_port.connected_device)
return 0
//Make sure are close enough for a valid connection
if(new_port.loc != loc)
return 0
//Perform the connection
connected_port = new_port
connected_port.connected_device = src
var/datum/pipeline/connected_port_parent = connected_port.PARENT1
connected_port_parent.reconcile_air()
anchored = 1 //Prevent movement
return 1
/obj/machinery/portable_atmospherics/proc/disconnect()
if(!connected_port)
return 0
anchored = 0
connected_port.connected_device = null
connected_port = null
return 1
/obj/machinery/portable_atmospherics/portableConnectorReturnAir()
return air_contents
/obj/machinery/portable_atmospherics/attackby(obj/item/weapon/W, mob/user, params)
if(istype(W, /obj/item/weapon/tank))
if(!(stat & BROKEN))
var/obj/item/weapon/tank/T = W
if(holding || !user.drop_item())
return
T.loc = src
holding = T
update_icon()
else if(istype(W, /obj/item/weapon/wrench))
if(!(stat & BROKEN))
if(connected_port)
disconnect()
playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
user.visible_message( \
"[user] disconnects [src].", \
"<span class='notice'>You unfasten [src] from the port.</span>", \
"<span class='italics'>You hear a ratchet.</span>")
update_icon()
return
else
var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc
if(!possible_port)
user << "<span class='notice'>Nothing happens.</span>"
return
if(!connect(possible_port))
user << "<span class='notice'>[name] failed to connect to the port.</span>"
return
playsound(src.loc, 'sound/items/Ratchet.ogg', 50, 1)
user.visible_message( \
"[user] connects [src].", \
"<span class='notice'>You fasten [src] to the port.</span>", \
"<span class='italics'>You hear a ratchet.</span>")
update_icon()
else if(istype(W, /obj/item/device/analyzer) && Adjacent(user))
atmosanalyzer_scan(air_contents, user)
else
return ..()
@@ -0,0 +1,141 @@
#define PUMP_OUT "out"
#define PUMP_IN "in"
#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE * 30)
#define PUMP_MIN_PRESSURE (ONE_ATMOSPHERE / 10)
#define PUMP_DEFAULT_PRESSURE (ONE_ATMOSPHERE)
/obj/machinery/portable_atmospherics/pump
name = "portable air pump"
icon_state = "psiphon:0"
density = 1
var/on = FALSE
var/direction = PUMP_OUT
var/obj/machinery/atmospherics/components/binary/pump/pump
volume = 1000
/obj/machinery/portable_atmospherics/pump/New()
..()
pump = new(src, FALSE)
pump.on = TRUE
pump.stat = 0
pump.build_network()
/obj/machinery/portable_atmospherics/pump/Destroy()
var/turf/T = get_turf(src)
T.assume_air(air_contents)
air_update_turf()
qdel(pump)
pump = null
return ..()
/obj/machinery/portable_atmospherics/pump/update_icon()
icon_state = "psiphon:[on]"
cut_overlays()
if(holding)
add_overlay("siphon-open")
if(connected_port)
add_overlay("siphon-connector")
/obj/machinery/portable_atmospherics/pump/process_atmos()
..()
if(!on)
pump.AIR1 = null
pump.AIR2 = null
return
var/turf/T = get_turf(src)
if(direction == PUMP_OUT) // Hook up the internal pump.
pump.AIR1 = holding ? holding.air_contents : air_contents
pump.AIR2 = holding ? air_contents : T.return_air()
else
pump.AIR1 = holding ? air_contents : T.return_air()
pump.AIR2 = holding ? holding.air_contents : air_contents
pump.process_atmos() // Pump gas.
if(!holding)
air_update_turf() // Update the environment if needed.
/obj/machinery/portable_atmospherics/pump/emp_act(severity)
if(is_operational())
if(prob(50 / severity))
on = !on
if(prob(100 / severity))
direction = PUMP_OUT
pump.target_pressure = rand(0, 100 * ONE_ATMOSPHERE)
update_icon()
..()
/obj/machinery/portable_atmospherics/pump/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = physical_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "portable_pump", name, 420, 415, master_ui, state)
ui.open()
/obj/machinery/portable_atmospherics/pump/ui_data()
var/data = list()
data["on"] = on
data["direction"] = direction
data["connected"] = connected_port ? 1 : 0
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
data["target_pressure"] = round(pump.target_pressure ? pump.target_pressure : 0)
data["default_pressure"] = round(PUMP_DEFAULT_PRESSURE)
data["min_pressure"] = round(PUMP_MIN_PRESSURE)
data["max_pressure"] = round(PUMP_MAX_PRESSURE)
if(holding)
data["holding"] = list()
data["holding"]["name"] = holding.name
data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
return data
/obj/machinery/portable_atmospherics/pump/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
if(on && !holding)
var/plasma = air_contents.gases["plasma"]
var/n2o = air_contents.gases["n2o"]
if(n2o || plasma)
message_admins("[key_name_admin(usr)] (<A HREF='?_src_=holder;adminmoreinfo=\ref[usr]'>?</A>) (<A HREF='?_src_=holder;adminplayerobservefollow=\ref[usr]'>FLW</A>) turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""]! (<A HREF='?_src_=holder;adminplayerobservecoodjump=1;X=[x];Y=[y];Z=[z]'>JMP</a>)")
log_admin("[key_name(usr)] turned on a pump that contains [n2o ? "N2O" : ""][n2o && plasma ? " & " : ""][plasma ? "Plasma" : ""] at [x], [y], [z]")
. = TRUE
if("direction")
if(direction == PUMP_OUT)
direction = PUMP_IN
else
direction = PUMP_OUT
. = TRUE
if("pressure")
var/pressure = params["pressure"]
if(pressure == "reset")
pressure = PUMP_DEFAULT_PRESSURE
. = TRUE
else if(pressure == "min")
pressure = PUMP_MIN_PRESSURE
. = TRUE
else if(pressure == "max")
pressure = PUMP_MAX_PRESSURE
. = TRUE
else if(pressure == "input")
pressure = input("New release pressure ([PUMP_MIN_PRESSURE]-[PUMP_MAX_PRESSURE] kPa):", name, pump.target_pressure) as num|null
if(!isnull(pressure) && !..())
. = TRUE
else if(text2num(pressure) != null)
pressure = text2num(pressure)
. = TRUE
if(.)
pump.target_pressure = Clamp(round(pressure), PUMP_MIN_PRESSURE, PUMP_MAX_PRESSURE)
investigate_log("was set to [pump.target_pressure] kPa by [key_name(usr)].", "atmos")
if("eject")
if(holding)
holding.loc = get_turf(src)
holding = null
. = TRUE
update_icon()
@@ -0,0 +1,135 @@
/obj/machinery/portable_atmospherics/scrubber
name = "portable air scrubber"
icon_state = "pscrubber:0"
density = 1
var/on = FALSE
var/volume_rate = 1000
volume = 1000
var/list/scrubbing = list("plasma", "co2", "n2o", "agent_b", "bz")
/obj/machinery/portable_atmospherics/scrubber/Destroy()
var/turf/T = get_turf(src)
T.assume_air(air_contents)
air_update_turf()
return ..()
/obj/machinery/portable_atmospherics/scrubber/update_icon()
icon_state = "pscrubber:[on]"
cut_overlays()
if(holding)
add_overlay("scrubber-open")
if(connected_port)
add_overlay("scrubber-connector")
/obj/machinery/portable_atmospherics/scrubber/process_atmos()
..()
if(!on)
return
if(holding)
scrub(holding.air_contents)
else
var/turf/T = get_turf(src)
scrub(T.return_air())
/obj/machinery/portable_atmospherics/scrubber/proc/scrub(var/datum/gas_mixture/mixture)
var/transfer_moles = min(1, volume_rate / mixture.volume) * mixture.total_moles()
var/datum/gas_mixture/filtering = mixture.remove(transfer_moles) // Remove part of the mixture to filter.
var/datum/gas_mixture/filtered = new
if(!filtering)
return
filtered.temperature = filtering.temperature
for(var/gas in filtering.gases & scrubbing)
filtered.add_gas(gas)
filtered.gases[gas][MOLES] = filtering.gases[gas][MOLES] // Shuffle the "bad" gasses to the filtered mixture.
filtering.gases[gas][MOLES] = 0
filtering.garbage_collect() // Now that the gasses are set to 0, clean up the mixture.
air_contents.merge(filtered) // Store filtered out gasses.
mixture.merge(filtering) // Returned the cleaned gas.
if(!holding)
air_update_turf()
/obj/machinery/portable_atmospherics/scrubber/emp_act(severity)
if(is_operational())
if(prob(50 / severity))
on = !on
update_icon()
..()
/obj/machinery/portable_atmospherics/scrubber/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, \
datum/tgui/master_ui = null, datum/ui_state/state = physical_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "portable_scrubber", name, 420, 335, master_ui, state)
ui.open()
/obj/machinery/portable_atmospherics/scrubber/ui_data()
var/data = list()
data["on"] = on
data["connected"] = connected_port ? 1 : 0
data["pressure"] = round(air_contents.return_pressure() ? air_contents.return_pressure() : 0)
if(holding)
data["holding"] = list()
data["holding"]["name"] = holding.name
data["holding"]["pressure"] = round(holding.air_contents.return_pressure())
return data
/obj/machinery/portable_atmospherics/scrubber/ui_act(action, params)
if(..())
return
switch(action)
if("power")
on = !on
. = TRUE
if("eject")
if(holding)
holding.loc = get_turf(src)
holding = null
. = TRUE
update_icon()
/obj/machinery/portable_atmospherics/scrubber/huge
name = "huge air scrubber"
icon_state = "scrubber:0"
anchored = TRUE
active_power_usage = 500
idle_power_usage = 10
volume_rate = 1500
volume = 50000
var/movable = FALSE
/obj/machinery/portable_atmospherics/scrubber/huge/movable
movable = TRUE
/obj/machinery/portable_atmospherics/scrubber/huge/update_icon()
icon_state = "scrubber:[on]"
/obj/machinery/portable_atmospherics/scrubber/huge/process_atmos()
if((!anchored && !movable) || !is_operational())
on = FALSE
update_icon()
use_power = 1 + on
if(!on)
return
..()
if(!holding)
var/turf/T = get_turf(src)
for(var/turf/AT in T.GetAtmosAdjacentTurfs(alldir = TRUE))
scrub(AT.return_air())
/obj/machinery/portable_atmospherics/scrubber/huge/attackby(obj/item/weapon/W, mob/user)
if(default_unfasten_wrench(user, W))
if(!movable)
on = FALSE
else
return ..()