diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index b5ba44d956..928837979c 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -1,8 +1,5 @@ //LISTMOS //indices of values in gas lists. -#define MOLES 1 -#define ARCHIVE 2 -#define GAS_META 3 #define META_GAS_SPECIFIC_HEAT 1 #define META_GAS_NAME 2 #define META_GAS_MOLES_VISIBLE 3 @@ -242,20 +239,25 @@ //HELPERS #define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) - -#define ADD_GAS(gas_id, out_list)\ - var/list/tmp_gaslist = GLOB.gaslist_cache[gas_id]; out_list[gas_id] = tmp_gaslist.Copy(); - -#define ASSERT_GAS(gas_id, gas_mixture) if (!gas_mixture.gases[gas_id]) { ADD_GAS(gas_id, gas_mixture.gases) }; - +#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 */ //prefer this to gas_mixture/total_moles in performance critical areas #define TOTAL_MOLES(cached_gases, out_var)\ out_var = 0;\ for(var/total_moles_id in cached_gases){\ - out_var += cached_gases[total_moles_id][MOLES];\ + out_var += cached_gases[total_moles_id];\ } +//Unomos - So for whatever reason, garbage collection actually drastically decreases the cost of atmos later in the round. Turning this into a define yields massively improved performance. +#define GAS_GARBAGE_COLLECT(GASGASGAS)\ + var/list/CACHE_GAS = GASGASGAS;\ + for(var/id in CACHE_GAS){\ + if(QUANTIZE(CACHE_GAS[id]) <= 0)\ + CACHE_GAS -= id;\ + } + +#define ARCHIVE_TEMPERATURE(gas) gas.temperature_archived = gas.temperature GLOBAL_LIST_INIT(pipe_paint_colors, list( "amethyst" = rgb(130,43,255), //supplymain diff --git a/code/datums/diseases/advance/symptoms/heal.dm b/code/datums/diseases/advance/symptoms/heal.dm index 0ace2ca8fd..0a4d192319 100644 --- a/code/datums/diseases/advance/symptoms/heal.dm +++ b/code/datums/diseases/advance/symptoms/heal.dm @@ -364,15 +364,15 @@ /datum/symptom/heal/plasma/CanHeal(datum/disease/advance/A) var/mob/living/M = A.affected_mob var/datum/gas_mixture/environment - var/list/gases + var/plasmamount . = 0 if(M.loc) environment = M.loc.return_air() if(environment) - gases = environment.gases - if(gases["plasma"] && gases["plasma"][MOLES] > gases["plasma"][GAS_META][META_GAS_MOLES_VISIBLE]) //if there's enough plasma in the air to see + plasmamount = environment.gases[/datum/gas/plasma] + if(plasmamount && plasmamount > GLOB.meta_gas_visibility[/datum/gas/plasma]) //if there's enough plasma in the air to see . += power * 0.5 if(M.reagents.has_reagent("plasma")) . += power * 0.75 diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index d673cdb16d..0623e2f5f9 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -103,11 +103,11 @@ // Can most things breathe? if(trace_gases) continue - if(!(A_gases[/datum/gas/oxygen] && A_gases[/datum/gas/oxygen][MOLES] >= 16)) + if(A_gases[/datum/gas/oxygen] >= 16) continue if(A_gases[/datum/gas/plasma]) continue - if(A_gases[/datum/gas/carbon_dioxide] && A_gases[/datum/gas/carbon_dioxide][MOLES] >= 10) + if(A_gases[/datum/gas/carbon_dioxide] >= 10) continue // Aim for goldilocks temperatures and pressure diff --git a/code/game/gamemodes/objective_items.dm b/code/game/gamemodes/objective_items.dm index 6603bb3604..e56495d808 100644 --- a/code/game/gamemodes/objective_items.dm +++ b/code/game/gamemodes/objective_items.dm @@ -124,7 +124,7 @@ /datum/objective_item/steal/plasma/check_special_completion(obj/item/tank/T) var/target_amount = text2num(name) var/found_amount = 0 - found_amount += T.air_contents.gases[/datum/gas/plasma] ? T.air_contents.gases[/datum/gas/plasma][MOLES] : 0 + found_amount += T.air_contents.gases[/datum/gas/plasma] return found_amount>=target_amount diff --git a/code/game/machinery/computer/atmos_control.dm b/code/game/machinery/computer/atmos_control.dm index baa6fa577e..6576f51e46 100644 --- a/code/game/machinery/computer/atmos_control.dm +++ b/code/game/machinery/computer/atmos_control.dm @@ -59,8 +59,8 @@ var/total_moles = air_sample.total_moles() if(total_moles) for(var/gas_id in air_sample.gases) - var/gas_name = air_sample.gases[gas_id][GAS_META][META_GAS_NAME] - signal.data["gases"][gas_name] = air_sample.gases[gas_id][MOLES] / total_moles * 100 + var/gas_name = GLOB.meta_gas_names[gas_id] + signal.data["gases"][gas_name] = air_sample.gases[gas_id] / total_moles * 100 radio_connection.post_signal(src, signal, filter = RADIO_ATMOSIA) diff --git a/code/game/mecha/equipment/tools/other_tools.dm b/code/game/mecha/equipment/tools/other_tools.dm index 1a3886c3df..80c92c7922 100644 --- a/code/game/mecha/equipment/tools/other_tools.dm +++ b/code/game/mecha/equipment/tools/other_tools.dm @@ -421,14 +421,13 @@ if(!istype(T)) return var/datum/gas_mixture/GM = new - GM.add_gas(/datum/gas/plasma) if(prob(10)) - GM.gases[/datum/gas/plasma][MOLES] += 100 + GM.gases[/datum/gas/plasma] += 100 GM.temperature = 1500+T0C //should be enough to start a fire T.visible_message("[src] suddenly disgorges a cloud of heated plasma.") qdel(src) else - GM.gases[/datum/gas/plasma][MOLES] += 5 + GM.gases[/datum/gas/plasma] += 5 GM.temperature = istype(T) ? T.air.return_temperature() : T20C T.visible_message("[src] suddenly disgorges a cloud of plasma.") T.assume_air(GM) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index d5de4a4898..fcbed0fc4a 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -237,9 +237,8 @@ cabin_air = new cabin_air.temperature = T20C cabin_air.volume = 200 - cabin_air.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - cabin_air.gases[/datum/gas/oxygen][MOLES] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) - cabin_air.gases[/datum/gas/nitrogen][MOLES] = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.gases[/datum/gas/oxygen] = O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) + cabin_air.gases[/datum/gas/nitrogen] = N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature) return cabin_air /obj/mecha/proc/add_radio() diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm index 264715e2ac..10ee73915c 100644 --- a/code/game/objects/effects/effect_system/effects_foam.dm +++ b/code/game/objects/effects/effect_system/effects_foam.dm @@ -1,347 +1,347 @@ -// Foam -// Similar to smoke, but slower and mobs absorb its reagent through their exposed skin. -#define ALUMINUM_FOAM 1 -#define IRON_FOAM 2 -#define RESIN_FOAM 3 - - -/obj/effect/particle_effect/foam - name = "foam" - icon_state = "foam" - opacity = 0 - anchored = TRUE - density = FALSE - layer = EDGED_TURF_LAYER - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/amount = 3 - animate_movement = 0 - var/metal = 0 - var/lifetime = 40 - var/reagent_divisor = 7 - var/static/list/blacklisted_turfs = typecacheof(list( - /turf/open/space/transit, - /turf/open/chasm, - /turf/open/lava)) - -/obj/effect/particle_effect/foam/firefighting - name = "firefighting foam" - lifetime = 20 //doesn't last as long as normal foam - amount = 0 //no spread - var/absorbed_plasma = 0 - -/obj/effect/particle_effect/foam/firefighting/MakeSlippery() - return - -/obj/effect/particle_effect/foam/firefighting/process() - ..() - - var/turf/open/T = get_turf(src) - var/obj/effect/hotspot/hotspot = (locate(/obj/effect/hotspot) in T) - if(hotspot && istype(T) && T.air) - qdel(hotspot) - var/datum/gas_mixture/G = T.air - var/plas_amt = min(30,G.gases[/datum/gas/plasma][MOLES]) //Absorb some plasma - G.gases[/datum/gas/plasma][MOLES] -= plas_amt - absorbed_plasma += plas_amt - if(G.temperature > T20C) - G.temperature = max(G.temperature/2,T20C) - G.garbage_collect() - T.air_update_turf() - -/obj/effect/particle_effect/foam/firefighting/kill_foam() - STOP_PROCESSING(SSfastprocess, src) - - if(absorbed_plasma) - var/obj/effect/decal/cleanable/plasma/P = (locate(/obj/effect/decal/cleanable/plasma) in get_turf(src)) - if(!P) - P = new(loc) - P.reagents.add_reagent("stable_plasma", absorbed_plasma) - - flick("[icon_state]-disolve", src) - QDEL_IN(src, 5) - -/obj/effect/particle_effect/foam/firefighting/foam_mob(mob/living/L) - if(!istype(L)) - return - L.adjust_fire_stacks(-2) - L.ExtinguishMob() - -/obj/effect/particle_effect/foam/firefighting/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return - -/obj/effect/particle_effect/foam/metal - name = "aluminium foam" - metal = ALUMINUM_FOAM - icon_state = "mfoam" - -/obj/effect/particle_effect/foam/metal/MakeSlippery() - return - -/obj/effect/particle_effect/foam/metal/smart - name = "smart foam" - -/obj/effect/particle_effect/foam/metal/iron - name = "iron foam" - metal = IRON_FOAM - -/obj/effect/particle_effect/foam/metal/resin - name = "resin foam" - metal = RESIN_FOAM - -/obj/effect/particle_effect/foam/long_life - lifetime = 150 - -/obj/effect/particle_effect/foam/Initialize() - . = ..() - MakeSlippery() - create_reagents(1000) //limited by the size of the reagent holder anyway. - START_PROCESSING(SSfastprocess, src) - playsound(src, 'sound/effects/bubbles2.ogg', 80, 1, -3) - -/obj/effect/particle_effect/foam/proc/MakeSlippery() - AddComponent(/datum/component/slippery, 100) - -/obj/effect/particle_effect/foam/Destroy() - STOP_PROCESSING(SSfastprocess, src) - return ..() - - -/obj/effect/particle_effect/foam/proc/kill_foam() - STOP_PROCESSING(SSfastprocess, src) - switch(metal) - if(ALUMINUM_FOAM) - new /obj/structure/foamedmetal(get_turf(src)) - if(IRON_FOAM) - new /obj/structure/foamedmetal/iron(get_turf(src)) - if(RESIN_FOAM) - new /obj/structure/foamedmetal/resin(get_turf(src)) - flick("[icon_state]-disolve", src) - QDEL_IN(src, 5) - -/obj/effect/particle_effect/foam/smart/kill_foam() //Smart foam adheres to area borders for walls - STOP_PROCESSING(SSfastprocess, src) - if(metal) - var/turf/T = get_turf(src) - if(isspaceturf(T)) //Block up any exposed space - T.PlaceOnTop(/turf/open/floor/plating/foam) - for(var/direction in GLOB.cardinals) - var/turf/cardinal_turf = get_step(T, direction) - if(get_area(cardinal_turf) != get_area(T)) //We're at an area boundary, so let's block off this turf! - new/obj/structure/foamedmetal(T) - break - flick("[icon_state]-disolve", src) - QDEL_IN(src, 5) - -/obj/effect/particle_effect/foam/process() - lifetime-- - if(lifetime < 1) - kill_foam() - return - - var/fraction = 1/initial(reagent_divisor) - for(var/obj/O in range(0,src)) - if(O.type == src.type) - continue - if(isturf(O.loc)) - var/turf/T = O.loc - if(T.intact && O.level == 1) //hidden under the floor - continue - if(lifetime % reagent_divisor) - reagents.reaction(O, VAPOR, fraction) - var/hit = 0 - for(var/mob/living/L in range(0,src)) - hit += foam_mob(L) - if(hit) - lifetime++ //this is so the decrease from mobs hit and the natural decrease don't cumulate. - var/T = get_turf(src) - if(lifetime % reagent_divisor) - reagents.reaction(T, VAPOR, fraction) - - if(--amount < 0) - return - spread_foam() - -/obj/effect/particle_effect/foam/proc/foam_mob(mob/living/L) - if(lifetime<1) - return 0 - if(!istype(L)) - return 0 - var/fraction = 1/initial(reagent_divisor) - if(lifetime % reagent_divisor) - reagents.reaction(L, VAPOR, fraction) - lifetime-- - return 1 - -/obj/effect/particle_effect/foam/proc/spread_foam() - var/turf/t_loc = get_turf(src) - for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) - var/obj/effect/particle_effect/foam/foundfoam = locate() in T //Don't spread foam where there's already foam! - if(foundfoam) - continue - - if(is_type_in_typecache(T, blacklisted_turfs)) - continue - - for(var/mob/living/L in T) - foam_mob(L) - var/obj/effect/particle_effect/foam/F = new src.type(T) - F.amount = amount - reagents.copy_to(F, (reagents.total_volume)) - F.add_atom_colour(color, FIXED_COLOUR_PRIORITY) - F.metal = metal - - -/obj/effect/particle_effect/foam/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - if(prob(max(0, exposed_temperature - 475))) //foam dissolves when heated - kill_foam() - - -/obj/effect/particle_effect/foam/metal/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) - return - - -/////////////////////////////////////////////// -//FOAM EFFECT DATUM -/datum/effect_system/foam_spread - var/amount = 10 // the size of the foam spread. - var/obj/chemholder - effect_type = /obj/effect/particle_effect/foam - var/metal = 0 - - -/datum/effect_system/foam_spread/metal - effect_type = /obj/effect/particle_effect/foam/metal - - -/datum/effect_system/foam_spread/metal/smart - effect_type = /obj/effect/particle_effect/foam/smart - - -/datum/effect_system/foam_spread/long - effect_type = /obj/effect/particle_effect/foam/long_life - -/datum/effect_system/foam_spread/New() - ..() - chemholder = new /obj() - var/datum/reagents/R = new/datum/reagents(1000) - chemholder.reagents = R - R.my_atom = chemholder - -/datum/effect_system/foam_spread/Destroy() - qdel(chemholder) - chemholder = null - return ..() - -/datum/effect_system/foam_spread/set_up(amt=5, loca, datum/reagents/carry = null) - if(isturf(loca)) - location = loca - else - location = get_turf(loca) - - amount = round(sqrt(amt / 2), 1) - carry.copy_to(chemholder, carry.total_volume) - -/datum/effect_system/foam_spread/metal/set_up(amt=5, loca, datum/reagents/carry = null, metaltype) - ..() - metal = metaltype - -/datum/effect_system/foam_spread/start() - var/obj/effect/particle_effect/foam/F = new effect_type(location) - var/foamcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) - chemholder.reagents.copy_to(F, chemholder.reagents.total_volume/amount) - F.add_atom_colour(foamcolor, FIXED_COLOUR_PRIORITY) - F.amount = amount - F.metal = metal - - -////////////////////////////////////////////////////////// -// FOAM STRUCTURE. Formed by metal foams. Dense and opaque, but easy to break -/obj/structure/foamedmetal - icon = 'icons/effects/effects.dmi' - icon_state = "metalfoam" - density = TRUE - opacity = 1 // changed in New() - anchored = TRUE - layer = EDGED_TURF_LAYER - resistance_flags = FIRE_PROOF | ACID_PROOF - name = "foamed metal" - desc = "A lightweight foamed metal wall." - gender = PLURAL - max_integrity = 20 - CanAtmosPass = ATMOS_PASS_DENSITY - -/obj/structure/foamedmetal/Initialize() - . = ..() - air_update_turf(1) - -/obj/structure/foamedmetal/Move() - var/turf/T = loc - . = ..() - move_update_air(T) - -/obj/structure/foamedmetal/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/foamedmetal/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) - -/obj/structure/foamedmetal/attack_hand(mob/user) - . = ..() - if(.) - return - user.changeNext_move(CLICK_CD_MELEE) - user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - to_chat(user, "You hit [src] but bounce off it!") - playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) - -/obj/structure/foamedmetal/CanPass(atom/movable/mover, turf/target) - return !density - -/obj/structure/foamedmetal/iron - max_integrity = 50 - icon_state = "ironfoam" - -//Atmos Backpack Resin, transparent, prevents atmos and filters the air -/obj/structure/foamedmetal/resin - name = "\improper ATMOS Resin" - desc = "A lightweight, transparent resin used to suffocate fires, scrub the air of toxins, and restore the air to a safe temperature." - opacity = FALSE - icon_state = "atmos_resin" - alpha = 120 - max_integrity = 10 - -/obj/structure/foamedmetal/resin/Initialize() - . = ..() - if(isopenturf(loc)) - var/turf/open/O = loc - O.ClearWet() - if(O.air) - var/datum/gas_mixture/G = O.air - G.temperature = 293.15 - for(var/obj/effect/hotspot/H in O) - qdel(H) - var/list/G_gases = G.gases - for(var/I in G_gases) - if(I == /datum/gas/oxygen || I == /datum/gas/nitrogen) - continue - G_gases[I][MOLES] = 0 - G.garbage_collect() - O.air_update_turf() - for(var/obj/machinery/atmospherics/components/unary/U in O) - if(!U.welded) - U.welded = TRUE - U.update_icon() - U.visible_message("[U] sealed shut!") - for(var/mob/living/L in O) - L.ExtinguishMob() - for(var/obj/item/Item in O) - Item.extinguish() - -/obj/structure/foamedmetal/resin/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSGLASS)) - return TRUE - . = ..() - -#undef ALUMINUM_FOAM -#undef IRON_FOAM -#undef RESIN_FOAM +// Foam +// Similar to smoke, but slower and mobs absorb its reagent through their exposed skin. +#define ALUMINUM_FOAM 1 +#define IRON_FOAM 2 +#define RESIN_FOAM 3 + + +/obj/effect/particle_effect/foam + name = "foam" + icon_state = "foam" + opacity = 0 + anchored = TRUE + density = FALSE + layer = EDGED_TURF_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + var/amount = 3 + animate_movement = 0 + var/metal = 0 + var/lifetime = 40 + var/reagent_divisor = 7 + var/static/list/blacklisted_turfs = typecacheof(list( + /turf/open/space/transit, + /turf/open/chasm, + /turf/open/lava)) + +/obj/effect/particle_effect/foam/firefighting + name = "firefighting foam" + lifetime = 20 //doesn't last as long as normal foam + amount = 0 //no spread + var/absorbed_plasma = 0 + +/obj/effect/particle_effect/foam/firefighting/MakeSlippery() + return + +/obj/effect/particle_effect/foam/firefighting/process() + ..() + + var/turf/open/T = get_turf(src) + var/obj/effect/hotspot/hotspot = (locate(/obj/effect/hotspot) in T) + if(hotspot && istype(T) && T.air) + qdel(hotspot) + var/datum/gas_mixture/G = T.air + var/plas_amt = min(30,G.gases[/datum/gas/plasma]) //Absorb some plasma + G.gases[/datum/gas/plasma] -= plas_amt + absorbed_plasma += plas_amt + if(G.temperature > T20C) + G.temperature = max(G.temperature/2,T20C) + GAS_GARBAGE_COLLECT(G.gases) + T.air_update_turf() + +/obj/effect/particle_effect/foam/firefighting/kill_foam() + STOP_PROCESSING(SSfastprocess, src) + + if(absorbed_plasma) + var/obj/effect/decal/cleanable/plasma/P = (locate(/obj/effect/decal/cleanable/plasma) in get_turf(src)) + if(!P) + P = new(loc) + P.reagents.add_reagent("stable_plasma", absorbed_plasma) + + flick("[icon_state]-disolve", src) + QDEL_IN(src, 5) + +/obj/effect/particle_effect/foam/firefighting/foam_mob(mob/living/L) + if(!istype(L)) + return + L.adjust_fire_stacks(-2) + L.ExtinguishMob() + +/obj/effect/particle_effect/foam/firefighting/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + return + +/obj/effect/particle_effect/foam/metal + name = "aluminium foam" + metal = ALUMINUM_FOAM + icon_state = "mfoam" + +/obj/effect/particle_effect/foam/metal/MakeSlippery() + return + +/obj/effect/particle_effect/foam/metal/smart + name = "smart foam" + +/obj/effect/particle_effect/foam/metal/iron + name = "iron foam" + metal = IRON_FOAM + +/obj/effect/particle_effect/foam/metal/resin + name = "resin foam" + metal = RESIN_FOAM + +/obj/effect/particle_effect/foam/long_life + lifetime = 150 + +/obj/effect/particle_effect/foam/Initialize() + . = ..() + MakeSlippery() + create_reagents(1000) //limited by the size of the reagent holder anyway. + START_PROCESSING(SSfastprocess, src) + playsound(src, 'sound/effects/bubbles2.ogg', 80, 1, -3) + +/obj/effect/particle_effect/foam/proc/MakeSlippery() + AddComponent(/datum/component/slippery, 100) + +/obj/effect/particle_effect/foam/Destroy() + STOP_PROCESSING(SSfastprocess, src) + return ..() + + +/obj/effect/particle_effect/foam/proc/kill_foam() + STOP_PROCESSING(SSfastprocess, src) + switch(metal) + if(ALUMINUM_FOAM) + new /obj/structure/foamedmetal(get_turf(src)) + if(IRON_FOAM) + new /obj/structure/foamedmetal/iron(get_turf(src)) + if(RESIN_FOAM) + new /obj/structure/foamedmetal/resin(get_turf(src)) + flick("[icon_state]-disolve", src) + QDEL_IN(src, 5) + +/obj/effect/particle_effect/foam/smart/kill_foam() //Smart foam adheres to area borders for walls + STOP_PROCESSING(SSfastprocess, src) + if(metal) + var/turf/T = get_turf(src) + if(isspaceturf(T)) //Block up any exposed space + T.PlaceOnTop(/turf/open/floor/plating/foam) + for(var/direction in GLOB.cardinals) + var/turf/cardinal_turf = get_step(T, direction) + if(get_area(cardinal_turf) != get_area(T)) //We're at an area boundary, so let's block off this turf! + new/obj/structure/foamedmetal(T) + break + flick("[icon_state]-disolve", src) + QDEL_IN(src, 5) + +/obj/effect/particle_effect/foam/process() + lifetime-- + if(lifetime < 1) + kill_foam() + return + + var/fraction = 1/initial(reagent_divisor) + for(var/obj/O in range(0,src)) + if(O.type == src.type) + continue + if(isturf(O.loc)) + var/turf/T = O.loc + if(T.intact && O.level == 1) //hidden under the floor + continue + if(lifetime % reagent_divisor) + reagents.reaction(O, VAPOR, fraction) + var/hit = 0 + for(var/mob/living/L in range(0,src)) + hit += foam_mob(L) + if(hit) + lifetime++ //this is so the decrease from mobs hit and the natural decrease don't cumulate. + var/T = get_turf(src) + if(lifetime % reagent_divisor) + reagents.reaction(T, VAPOR, fraction) + + if(--amount < 0) + return + spread_foam() + +/obj/effect/particle_effect/foam/proc/foam_mob(mob/living/L) + if(lifetime<1) + return 0 + if(!istype(L)) + return 0 + var/fraction = 1/initial(reagent_divisor) + if(lifetime % reagent_divisor) + reagents.reaction(L, VAPOR, fraction) + lifetime-- + return 1 + +/obj/effect/particle_effect/foam/proc/spread_foam() + var/turf/t_loc = get_turf(src) + for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) + var/obj/effect/particle_effect/foam/foundfoam = locate() in T //Don't spread foam where there's already foam! + if(foundfoam) + continue + + if(is_type_in_typecache(T, blacklisted_turfs)) + continue + + for(var/mob/living/L in T) + foam_mob(L) + var/obj/effect/particle_effect/foam/F = new src.type(T) + F.amount = amount + reagents.copy_to(F, (reagents.total_volume)) + F.add_atom_colour(color, FIXED_COLOUR_PRIORITY) + F.metal = metal + + +/obj/effect/particle_effect/foam/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + if(prob(max(0, exposed_temperature - 475))) //foam dissolves when heated + kill_foam() + + +/obj/effect/particle_effect/foam/metal/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) + return + + +/////////////////////////////////////////////// +//FOAM EFFECT DATUM +/datum/effect_system/foam_spread + var/amount = 10 // the size of the foam spread. + var/obj/chemholder + effect_type = /obj/effect/particle_effect/foam + var/metal = 0 + + +/datum/effect_system/foam_spread/metal + effect_type = /obj/effect/particle_effect/foam/metal + + +/datum/effect_system/foam_spread/metal/smart + effect_type = /obj/effect/particle_effect/foam/smart + + +/datum/effect_system/foam_spread/long + effect_type = /obj/effect/particle_effect/foam/long_life + +/datum/effect_system/foam_spread/New() + ..() + chemholder = new /obj() + var/datum/reagents/R = new/datum/reagents(1000) + chemholder.reagents = R + R.my_atom = chemholder + +/datum/effect_system/foam_spread/Destroy() + qdel(chemholder) + chemholder = null + return ..() + +/datum/effect_system/foam_spread/set_up(amt=5, loca, datum/reagents/carry = null) + if(isturf(loca)) + location = loca + else + location = get_turf(loca) + + amount = round(sqrt(amt / 2), 1) + carry.copy_to(chemholder, carry.total_volume) + +/datum/effect_system/foam_spread/metal/set_up(amt=5, loca, datum/reagents/carry = null, metaltype) + ..() + metal = metaltype + +/datum/effect_system/foam_spread/start() + var/obj/effect/particle_effect/foam/F = new effect_type(location) + var/foamcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) + chemholder.reagents.copy_to(F, chemholder.reagents.total_volume/amount) + F.add_atom_colour(foamcolor, FIXED_COLOUR_PRIORITY) + F.amount = amount + F.metal = metal + + +////////////////////////////////////////////////////////// +// FOAM STRUCTURE. Formed by metal foams. Dense and opaque, but easy to break +/obj/structure/foamedmetal + icon = 'icons/effects/effects.dmi' + icon_state = "metalfoam" + density = TRUE + opacity = 1 // changed in New() + anchored = TRUE + layer = EDGED_TURF_LAYER + resistance_flags = FIRE_PROOF | ACID_PROOF + name = "foamed metal" + desc = "A lightweight foamed metal wall." + gender = PLURAL + max_integrity = 20 + CanAtmosPass = ATMOS_PASS_DENSITY + +/obj/structure/foamedmetal/Initialize() + . = ..() + air_update_turf(1) + +/obj/structure/foamedmetal/Move() + var/turf/T = loc + . = ..() + move_update_air(T) + +/obj/structure/foamedmetal/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/foamedmetal/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) + +/obj/structure/foamedmetal/attack_hand(mob/user) + . = ..() + if(.) + return + user.changeNext_move(CLICK_CD_MELEE) + user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + to_chat(user, "You hit [src] but bounce off it!") + playsound(src.loc, 'sound/weapons/tap.ogg', 100, 1) + +/obj/structure/foamedmetal/CanPass(atom/movable/mover, turf/target) + return !density + +/obj/structure/foamedmetal/iron + max_integrity = 50 + icon_state = "ironfoam" + +//Atmos Backpack Resin, transparent, prevents atmos and filters the air +/obj/structure/foamedmetal/resin + name = "\improper ATMOS Resin" + desc = "A lightweight, transparent resin used to suffocate fires, scrub the air of toxins, and restore the air to a safe temperature." + opacity = FALSE + icon_state = "atmos_resin" + alpha = 120 + max_integrity = 10 + +/obj/structure/foamedmetal/resin/Initialize() + . = ..() + if(isopenturf(loc)) + var/turf/open/O = loc + O.ClearWet() + if(O.air) + var/datum/gas_mixture/G = O.air + G.temperature = 293.15 + for(var/obj/effect/hotspot/H in O) + qdel(H) + var/list/G_gases = G.gases + for(var/I in G_gases) + if(I == /datum/gas/oxygen || I == /datum/gas/nitrogen) + continue + G_gases[I] = 0 + GAS_GARBAGE_COLLECT(G.gases) + O.air_update_turf() + for(var/obj/machinery/atmospherics/components/unary/U in O) + if(!U.welded) + U.welded = TRUE + U.update_icon() + U.visible_message("[U] sealed shut!") + for(var/mob/living/L in O) + L.ExtinguishMob() + for(var/obj/item/Item in O) + Item.extinguish() + +/obj/structure/foamedmetal/resin/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSGLASS)) + return TRUE + . = ..() + +#undef ALUMINUM_FOAM +#undef IRON_FOAM +#undef RESIN_FOAM diff --git a/code/game/objects/effects/effect_system/effects_smoke.dm b/code/game/objects/effects/effect_system/effects_smoke.dm index e8e833890b..79deac475b 100644 --- a/code/game/objects/effects/effect_system/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/effects_smoke.dm @@ -1,329 +1,328 @@ -///////////////////////////////////////////// -//// SMOKE SYSTEMS -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke - name = "smoke" - icon = 'icons/effects/96x96.dmi' - icon_state = "smoke" - pixel_x = -32 - pixel_y = -32 - opacity = 0 - layer = FLY_LAYER - anchored = TRUE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - animate_movement = 0 - var/amount = 4 - var/lifetime = 5 - var/opaque = 1 //whether the smoke can block the view when in enough amount - - -/obj/effect/particle_effect/smoke/proc/fade_out(frames = 16) - if(alpha == 0) //Handle already transparent case - return - if(frames == 0) - frames = 1 //We will just assume that by 0 frames, the coder meant "during one frame". - var/step = alpha / frames - for(var/i = 0, i < frames, i++) - alpha -= step - if(alpha < 160) - set_opacity(0) //if we were blocking view, we aren't now because we're fading out - stoplag() - -/obj/effect/particle_effect/smoke/Initialize() - . = ..() - create_reagents(500) - START_PROCESSING(SSobj, src) - - -/obj/effect/particle_effect/smoke/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/effect/particle_effect/smoke/proc/kill_smoke() - STOP_PROCESSING(SSobj, src) - INVOKE_ASYNC(src, .proc/fade_out) - QDEL_IN(src, 10) - -/obj/effect/particle_effect/smoke/process() - lifetime-- - if(lifetime < 1) - kill_smoke() - return 0 - for(var/mob/living/L in range(0,src)) - smoke_mob(L) - return 1 - -/obj/effect/particle_effect/smoke/proc/smoke_mob(mob/living/carbon/C) - if(!istype(C)) - return 0 - if(lifetime<1) - return 0 - if(C.internal != null || C.has_smoke_protection()) - return 0 - if(C.smoke_delay) - return 0 - C.smoke_delay++ - addtimer(CALLBACK(src, .proc/remove_smoke_delay, C), 10) - return 1 - -/obj/effect/particle_effect/smoke/proc/remove_smoke_delay(mob/living/carbon/C) - if(C) - C.smoke_delay = 0 - -/obj/effect/particle_effect/smoke/proc/spread_smoke() - var/turf/t_loc = get_turf(src) - if(!t_loc) - return - var/list/newsmokes = list() - for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) - var/obj/effect/particle_effect/smoke/foundsmoke = locate() in T //Don't spread smoke where there's already smoke! - if(foundsmoke) - continue - for(var/mob/living/L in T) - smoke_mob(L) - var/obj/effect/particle_effect/smoke/S = new type(T) - reagents.copy_to(S, reagents.total_volume) - S.setDir(pick(GLOB.cardinals)) - S.amount = amount-1 - S.add_atom_colour(color, FIXED_COLOUR_PRIORITY) - S.lifetime = lifetime - if(S.amount>0) - if(opaque) - S.set_opacity(TRUE) - newsmokes.Add(S) - - if(newsmokes.len) - spawn(1) //the smoke spreads rapidly but not instantly - for(var/obj/effect/particle_effect/smoke/SM in newsmokes) - SM.spread_smoke() - - -/datum/effect_system/smoke_spread - var/amount = 10 - effect_type = /obj/effect/particle_effect/smoke - -/datum/effect_system/smoke_spread/set_up(radius = 5, loca) - if(isturf(loca)) - location = loca - else - location = get_turf(loca) - amount = radius - -/datum/effect_system/smoke_spread/start() - if(holder) - location = get_turf(holder) - var/obj/effect/particle_effect/smoke/S = new effect_type(location) - S.amount = amount - if(S.amount) - S.spread_smoke() - - -///////////////////////////////////////////// -// Bad smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/bad - lifetime = 8 - -/obj/effect/particle_effect/smoke/bad/smoke_mob(mob/living/carbon/M) - if(..()) - M.drop_all_held_items() - M.adjustOxyLoss(1) - M.emote("cough") - return 1 - -/obj/effect/particle_effect/smoke/bad/CanPass(atom/movable/mover, turf/target) - if(istype(mover, /obj/item/projectile/beam)) - var/obj/item/projectile/beam/B = mover - B.damage = (B.damage/2) - return 1 - - - -/datum/effect_system/smoke_spread/bad - effect_type = /obj/effect/particle_effect/smoke/bad - -///////////////////////////////////////////// -// Nanofrost smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/freezing - name = "nanofrost smoke" - color = "#B2FFFF" - opaque = 0 - -/datum/effect_system/smoke_spread/freezing - effect_type = /obj/effect/particle_effect/smoke/freezing - var/blast = 0 - var/temperature = 2 - var/weldvents = TRUE - var/distcheck = TRUE - -/datum/effect_system/smoke_spread/freezing/proc/Chilled(atom/A) - if(isopenturf(A)) - var/turf/open/T = A - if(T.air) - var/datum/gas_mixture/G = T.air - if(!distcheck || get_dist(T, location) < blast) // Otherwise we'll get silliness like people using Nanofrost to kill people through walls with cold air - G.temperature = temperature - T.air_update_turf() - for(var/obj/effect/hotspot/H in T) - qdel(H) - var/list/G_gases = G.gases - if(G_gases[/datum/gas/plasma]) - G.assert_gas(/datum/gas/nitrogen) - G_gases[/datum/gas/nitrogen][MOLES] += (G_gases[/datum/gas/plasma][MOLES]) - G_gases[/datum/gas/plasma][MOLES] = 0 - G.garbage_collect() - if (weldvents) - for(var/obj/machinery/atmospherics/components/unary/U in T) - if(!isnull(U.welded) && !U.welded) //must be an unwelded vent pump or vent scrubber. - U.welded = TRUE - U.update_icon() - U.visible_message("[U] was frozen shut!") - for(var/mob/living/L in T) - L.ExtinguishMob() - for(var/obj/item/Item in T) - Item.extinguish() - -/datum/effect_system/smoke_spread/freezing/set_up(radius = 5, loca, blast_radius = 0) - ..() - blast = blast_radius - -/datum/effect_system/smoke_spread/freezing/start() - if(blast) - for(var/turf/T in RANGE_TURFS(blast, location)) - Chilled(T) - ..() - -/datum/effect_system/smoke_spread/freezing/decon - temperature = 293.15 - distcheck = FALSE - weldvents = FALSE - - -///////////////////////////////////////////// -// Sleep smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/sleeping - color = "#9C3636" - lifetime = 10 - -/obj/effect/particle_effect/smoke/sleeping/smoke_mob(mob/living/carbon/M) - if(..()) - M.Sleeping(200) - M.emote("cough") - return 1 - -/datum/effect_system/smoke_spread/sleeping - effect_type = /obj/effect/particle_effect/smoke/sleeping - -///////////////////////////////////////////// -// Chem smoke -///////////////////////////////////////////// - -/obj/effect/particle_effect/smoke/chem - lifetime = 10 - - -/obj/effect/particle_effect/smoke/chem/process() - if(..()) - var/turf/T = get_turf(src) - var/fraction = 1/initial(lifetime) - for(var/atom/movable/AM in T) - if(AM.type == src.type) - continue - if(T.intact && AM.level == 1) //hidden under the floor - continue - reagents.reaction(AM, TOUCH, fraction) - - reagents.reaction(T, TOUCH, fraction) - return 1 - -/obj/effect/particle_effect/smoke/chem/smoke_mob(mob/living/carbon/M) - if(lifetime<1) - return 0 - if(!istype(M)) - return 0 - var/mob/living/carbon/C = M - if(C.internal != null || C.has_smoke_protection()) - return 0 - var/fraction = 1/initial(lifetime) - reagents.copy_to(C, fraction*reagents.total_volume) - reagents.reaction(M, INGEST, fraction) - return 1 - - - -/datum/effect_system/smoke_spread/chem - var/obj/chemholder - effect_type = /obj/effect/particle_effect/smoke/chem - -/datum/effect_system/smoke_spread/chem/New() - ..() - chemholder = new /obj() - var/datum/reagents/R = new/datum/reagents(500) - chemholder.reagents = R - R.my_atom = chemholder - -/datum/effect_system/smoke_spread/chem/Destroy() - qdel(chemholder) - chemholder = null - return ..() - -/datum/effect_system/smoke_spread/chem/set_up(datum/reagents/carry = null, radius = 1, loca, silent = FALSE) - if(isturf(loca)) - location = loca - else - location = get_turf(loca) - amount = radius - carry.copy_to(chemholder, carry.total_volume) - - if(!silent) - var/contained = "" - for(var/reagent in carry.reagent_list) - contained += " [reagent] " - if(contained) - contained = "\[[contained]\]" - - var/where = "[AREACOORD(location)]" - if(carry.my_atom.fingerprintslast) - var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast) - var/more = "" - if(M) - more = "[ADMIN_LOOKUPFLW(M)] " - message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. Key: [more ? more : carry.my_atom.fingerprintslast].") - log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last touched by [carry.my_atom.fingerprintslast].") - else - message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. No associated key.") - log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.") - - -/datum/effect_system/smoke_spread/chem/start() - var/mixcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) - if(holder) - location = get_turf(holder) - var/obj/effect/particle_effect/smoke/chem/S = new effect_type(location) - - if(chemholder.reagents.total_volume > 1) // can't split 1 very well - chemholder.reagents.copy_to(S, chemholder.reagents.total_volume) - - if(mixcolor) - S.add_atom_colour(mixcolor, FIXED_COLOUR_PRIORITY) // give the smoke color, if it has any to begin with - S.amount = amount - if(S.amount) - S.spread_smoke() //calling process right now so the smoke immediately attacks mobs. - - -///////////////////////////////////////////// -// Transparent smoke -///////////////////////////////////////////// - -//Same as the base type, but the smoke produced is not opaque -/datum/effect_system/smoke_spread/transparent - effect_type = /obj/effect/particle_effect/smoke/transparent - -/obj/effect/particle_effect/smoke/transparent - opaque = FALSE +///////////////////////////////////////////// +//// SMOKE SYSTEMS +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke + name = "smoke" + icon = 'icons/effects/96x96.dmi' + icon_state = "smoke" + pixel_x = -32 + pixel_y = -32 + opacity = 0 + layer = FLY_LAYER + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + animate_movement = 0 + var/amount = 4 + var/lifetime = 5 + var/opaque = 1 //whether the smoke can block the view when in enough amount + + +/obj/effect/particle_effect/smoke/proc/fade_out(frames = 16) + if(alpha == 0) //Handle already transparent case + return + if(frames == 0) + frames = 1 //We will just assume that by 0 frames, the coder meant "during one frame". + var/step = alpha / frames + for(var/i = 0, i < frames, i++) + alpha -= step + if(alpha < 160) + set_opacity(0) //if we were blocking view, we aren't now because we're fading out + stoplag() + +/obj/effect/particle_effect/smoke/Initialize() + . = ..() + create_reagents(500) + START_PROCESSING(SSobj, src) + + +/obj/effect/particle_effect/smoke/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/effect/particle_effect/smoke/proc/kill_smoke() + STOP_PROCESSING(SSobj, src) + INVOKE_ASYNC(src, .proc/fade_out) + QDEL_IN(src, 10) + +/obj/effect/particle_effect/smoke/process() + lifetime-- + if(lifetime < 1) + kill_smoke() + return 0 + for(var/mob/living/L in range(0,src)) + smoke_mob(L) + return 1 + +/obj/effect/particle_effect/smoke/proc/smoke_mob(mob/living/carbon/C) + if(!istype(C)) + return 0 + if(lifetime<1) + return 0 + if(C.internal != null || C.has_smoke_protection()) + return 0 + if(C.smoke_delay) + return 0 + C.smoke_delay++ + addtimer(CALLBACK(src, .proc/remove_smoke_delay, C), 10) + return 1 + +/obj/effect/particle_effect/smoke/proc/remove_smoke_delay(mob/living/carbon/C) + if(C) + C.smoke_delay = 0 + +/obj/effect/particle_effect/smoke/proc/spread_smoke() + var/turf/t_loc = get_turf(src) + if(!t_loc) + return + var/list/newsmokes = list() + for(var/turf/T in t_loc.GetAtmosAdjacentTurfs()) + var/obj/effect/particle_effect/smoke/foundsmoke = locate() in T //Don't spread smoke where there's already smoke! + if(foundsmoke) + continue + for(var/mob/living/L in T) + smoke_mob(L) + var/obj/effect/particle_effect/smoke/S = new type(T) + reagents.copy_to(S, reagents.total_volume) + S.setDir(pick(GLOB.cardinals)) + S.amount = amount-1 + S.add_atom_colour(color, FIXED_COLOUR_PRIORITY) + S.lifetime = lifetime + if(S.amount>0) + if(opaque) + S.set_opacity(TRUE) + newsmokes.Add(S) + + if(newsmokes.len) + spawn(1) //the smoke spreads rapidly but not instantly + for(var/obj/effect/particle_effect/smoke/SM in newsmokes) + SM.spread_smoke() + + +/datum/effect_system/smoke_spread + var/amount = 10 + effect_type = /obj/effect/particle_effect/smoke + +/datum/effect_system/smoke_spread/set_up(radius = 5, loca) + if(isturf(loca)) + location = loca + else + location = get_turf(loca) + amount = radius + +/datum/effect_system/smoke_spread/start() + if(holder) + location = get_turf(holder) + var/obj/effect/particle_effect/smoke/S = new effect_type(location) + S.amount = amount + if(S.amount) + S.spread_smoke() + + +///////////////////////////////////////////// +// Bad smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/bad + lifetime = 8 + +/obj/effect/particle_effect/smoke/bad/smoke_mob(mob/living/carbon/M) + if(..()) + M.drop_all_held_items() + M.adjustOxyLoss(1) + M.emote("cough") + return 1 + +/obj/effect/particle_effect/smoke/bad/CanPass(atom/movable/mover, turf/target) + if(istype(mover, /obj/item/projectile/beam)) + var/obj/item/projectile/beam/B = mover + B.damage = (B.damage/2) + return 1 + + + +/datum/effect_system/smoke_spread/bad + effect_type = /obj/effect/particle_effect/smoke/bad + +///////////////////////////////////////////// +// Nanofrost smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/freezing + name = "nanofrost smoke" + color = "#B2FFFF" + opaque = 0 + +/datum/effect_system/smoke_spread/freezing + effect_type = /obj/effect/particle_effect/smoke/freezing + var/blast = 0 + var/temperature = 2 + var/weldvents = TRUE + var/distcheck = TRUE + +/datum/effect_system/smoke_spread/freezing/proc/Chilled(atom/A) + if(isopenturf(A)) + var/turf/open/T = A + if(T.air) + var/datum/gas_mixture/G = T.air + if(!distcheck || get_dist(T, location) < blast) // Otherwise we'll get silliness like people using Nanofrost to kill people through walls with cold air + G.temperature = temperature + T.air_update_turf() + for(var/obj/effect/hotspot/H in T) + qdel(H) + var/list/G_gases = G.gases + if(G_gases[/datum/gas/plasma]) + G_gases[/datum/gas/nitrogen] += (G_gases[/datum/gas/plasma]) + G_gases[/datum/gas/plasma] = 0 + GAS_GARBAGE_COLLECT(G.gases) + if (weldvents) + for(var/obj/machinery/atmospherics/components/unary/U in T) + if(!isnull(U.welded) && !U.welded) //must be an unwelded vent pump or vent scrubber. + U.welded = TRUE + U.update_icon() + U.visible_message("[U] was frozen shut!") + for(var/mob/living/L in T) + L.ExtinguishMob() + for(var/obj/item/Item in T) + Item.extinguish() + +/datum/effect_system/smoke_spread/freezing/set_up(radius = 5, loca, blast_radius = 0) + ..() + blast = blast_radius + +/datum/effect_system/smoke_spread/freezing/start() + if(blast) + for(var/turf/T in RANGE_TURFS(blast, location)) + Chilled(T) + ..() + +/datum/effect_system/smoke_spread/freezing/decon + temperature = 293.15 + distcheck = FALSE + weldvents = FALSE + + +///////////////////////////////////////////// +// Sleep smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/sleeping + color = "#9C3636" + lifetime = 10 + +/obj/effect/particle_effect/smoke/sleeping/smoke_mob(mob/living/carbon/M) + if(..()) + M.Sleeping(200) + M.emote("cough") + return 1 + +/datum/effect_system/smoke_spread/sleeping + effect_type = /obj/effect/particle_effect/smoke/sleeping + +///////////////////////////////////////////// +// Chem smoke +///////////////////////////////////////////// + +/obj/effect/particle_effect/smoke/chem + lifetime = 10 + + +/obj/effect/particle_effect/smoke/chem/process() + if(..()) + var/turf/T = get_turf(src) + var/fraction = 1/initial(lifetime) + for(var/atom/movable/AM in T) + if(AM.type == src.type) + continue + if(T.intact && AM.level == 1) //hidden under the floor + continue + reagents.reaction(AM, TOUCH, fraction) + + reagents.reaction(T, TOUCH, fraction) + return 1 + +/obj/effect/particle_effect/smoke/chem/smoke_mob(mob/living/carbon/M) + if(lifetime<1) + return 0 + if(!istype(M)) + return 0 + var/mob/living/carbon/C = M + if(C.internal != null || C.has_smoke_protection()) + return 0 + var/fraction = 1/initial(lifetime) + reagents.copy_to(C, fraction*reagents.total_volume) + reagents.reaction(M, INGEST, fraction) + return 1 + + + +/datum/effect_system/smoke_spread/chem + var/obj/chemholder + effect_type = /obj/effect/particle_effect/smoke/chem + +/datum/effect_system/smoke_spread/chem/New() + ..() + chemholder = new /obj() + var/datum/reagents/R = new/datum/reagents(500) + chemholder.reagents = R + R.my_atom = chemholder + +/datum/effect_system/smoke_spread/chem/Destroy() + qdel(chemholder) + chemholder = null + return ..() + +/datum/effect_system/smoke_spread/chem/set_up(datum/reagents/carry = null, radius = 1, loca, silent = FALSE) + if(isturf(loca)) + location = loca + else + location = get_turf(loca) + amount = radius + carry.copy_to(chemholder, carry.total_volume) + + if(!silent) + var/contained = "" + for(var/reagent in carry.reagent_list) + contained += " [reagent] " + if(contained) + contained = "\[[contained]\]" + + var/where = "[AREACOORD(location)]" + if(carry.my_atom.fingerprintslast) + var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast) + var/more = "" + if(M) + more = "[ADMIN_LOOKUPFLW(M)] " + message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. Key: [more ? more : carry.my_atom.fingerprintslast].") + log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last touched by [carry.my_atom.fingerprintslast].") + else + message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. No associated key.") + log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.") + + +/datum/effect_system/smoke_spread/chem/start() + var/mixcolor = mix_color_from_reagents(chemholder.reagents.reagent_list) + if(holder) + location = get_turf(holder) + var/obj/effect/particle_effect/smoke/chem/S = new effect_type(location) + + if(chemholder.reagents.total_volume > 1) // can't split 1 very well + chemholder.reagents.copy_to(S, chemholder.reagents.total_volume) + + if(mixcolor) + S.add_atom_colour(mixcolor, FIXED_COLOUR_PRIORITY) // give the smoke color, if it has any to begin with + S.amount = amount + if(S.amount) + S.spread_smoke() //calling process right now so the smoke immediately attacks mobs. + + +///////////////////////////////////////////// +// Transparent smoke +///////////////////////////////////////////// + +//Same as the base type, but the smoke produced is not opaque +/datum/effect_system/smoke_spread/transparent + effect_type = /obj/effect/particle_effect/smoke/transparent + +/obj/effect/particle_effect/smoke/transparent + opaque = FALSE diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm index b1bb3e6b4d..65395d534a 100644 --- a/code/game/objects/effects/spawners/bombspawner.dm +++ b/code/game/objects/effects/spawners/bombspawner.dm @@ -1,6 +1,6 @@ #define CELSIUS_TO_KELVIN(T_K) ((T_K) + T0C) -#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT]) / (((PRESSURE_P) * GLOB.meta_gas_info[/datum/gas/plasma][META_GAS_SPECIFIC_HEAT] + (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_info[/datum/gas/oxygen][META_GAS_SPECIFIC_HEAT] / CELSIUS_TO_KELVIN(TEMP_O))) +#define OPTIMAL_TEMP_K_PLA_BURN_SCALE(PRESSURE_P,PRESSURE_O,TEMP_O) (((PRESSURE_P) * GLOB.meta_gas_specific_heats[/datum/gas/plasma]) / (((PRESSURE_P) * GLOB.meta_gas_specific_heats[/datum/gas/plasma] + (PRESSURE_O) * GLOB.meta_gas_specific_heats[/datum/gas/oxygen]) / PLASMA_UPPER_TEMPERATURE - (PRESSURE_O) * GLOB.meta_gas_specific_heats[/datum/gas/oxygen] / CELSIUS_TO_KELVIN(TEMP_O))) #define OPTIMAL_TEMP_K_PLA_BURN_RATIO(PRESSURE_P,PRESSURE_O,TEMP_O) (CELSIUS_TO_KELVIN(TEMP_O) * PLASMA_OXYGEN_FULLBURN * (PRESSURE_P) / (PRESSURE_O)) /obj/effect/spawner/newbomb @@ -19,12 +19,10 @@ var/obj/item/tank/internals/plasma/PT = new(V) var/obj/item/tank/internals/oxygen/OT = new(V) - PT.air_contents.assert_gas(/datum/gas/plasma) - PT.air_contents.gases[/datum/gas/plasma][MOLES] = pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p)) + PT.air_contents.gases[/datum/gas/plasma] = pressure_p*PT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_p)) PT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_p) - OT.air_contents.assert_gas(/datum/gas/oxygen) - OT.air_contents.gases[/datum/gas/oxygen][MOLES] = pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o)) + OT.air_contents.gases[/datum/gas/oxygen] = pressure_o*OT.volume/(R_IDEAL_GAS_EQUATION*CELSIUS_TO_KELVIN(temp_o)) OT.air_contents.temperature = CELSIUS_TO_KELVIN(temp_o) V.tank_one = PT diff --git a/code/game/objects/items/chrono_eraser.dm b/code/game/objects/items/chrono_eraser.dm index f7c37715aa..344e7c5472 100644 --- a/code/game/objects/items/chrono_eraser.dm +++ b/code/game/objects/items/chrono_eraser.dm @@ -248,9 +248,8 @@ /obj/effect/chrono_field/return_air() //we always have nominal air and temperature var/datum/gas_mixture/GM = new - GM.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - GM.gases[/datum/gas/oxygen][MOLES] = MOLES_O2STANDARD - GM.gases[/datum/gas/nitrogen][MOLES] = MOLES_N2STANDARD + GM.gases[/datum/gas/oxygen] = MOLES_O2STANDARD + GM.gases[/datum/gas/nitrogen] = MOLES_N2STANDARD GM.temperature = T20C return GM diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 7d3d36c5b2..48b21d58bd 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -358,9 +358,9 @@ GLOBAL_LIST_EMPTY(PDAs) if (total_moles) for(var/id in env_gases) - var/gas_level = env_gases[id][MOLES]/total_moles + var/gas_level = env_gases[id]/total_moles if(gas_level > 0) - dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100, 0.01)]%
" + dat += "[GLOB.meta_gas_names[id]]: [round(gas_level*100, 0.01)]%
" dat += "Temperature: [round(environment.temperature-T0C)]°C
" dat += "
" diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 79b04c771b..2df0ad0fbb 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -428,39 +428,38 @@ SLIME SCANNER if(total_moles) var/list/env_gases = environment.gases - environment.assert_gases(arglist(GLOB.hardcoded_gases)) - var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles + var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles + var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles + var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles + var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)") + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)") else - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen][MOLES], 0.01)] mol)") + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/nitrogen], 0.01)] mol)") if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 0.01)] mol)") + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)") else - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen][MOLES], 0.01)] mol)") + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/oxygen], 0.01)] mol)") if(co2_concentration > 0.01) - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)") + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)") else - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide][MOLES], 0.01)] mol)") + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/carbon_dioxide], 0.01)] mol)") if(plasma_concentration > 0.005) - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)") + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)") else - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma][MOLES], 0.01)] mol)") + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] % ([round(env_gases[/datum/gas/plasma], 0.01)] mol)") - environment.garbage_collect() + GAS_GARBAGE_COLLECT(environment.gases) for(var/id in env_gases) if(id in GLOB.hardcoded_gases) continue - var/gas_concentration = env_gases[id][MOLES]/total_moles - to_chat(user, "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id][MOLES], 0.01)] mol)") + var/gas_concentration = env_gases[id]/total_moles + to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(env_gases[id], 0.01)] mol)") to_chat(user, "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)") /obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens @@ -550,8 +549,8 @@ SLIME SCANNER var/list/cached_gases = air_contents.gases for(var/id in cached_gases) - var/gas_concentration = cached_gases[id][MOLES]/total_moles - to_chat(user, "[cached_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id][MOLES], 0.01)] mol)") + var/gas_concentration = cached_gases[id]/total_moles + to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] % ([round(cached_gases[id], 0.01)] mol)") to_chat(user, "Temperature: [round(temperature - T0C,0.01)] °C ([round(temperature, 0.01)] K)") else diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index 2635955a15..fd51ecf26b 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -205,7 +205,7 @@ //Transfer 5% of current tank air contents to turf var/datum/gas_mixture/air_transfer = ptank.air_contents.remove_ratio(release_amount) if(air_transfer.gases[/datum/gas/plasma]) - air_transfer.gases[/datum/gas/plasma][MOLES] *= 5 + air_transfer.gases[/datum/gas/plasma] *= 5 target.assume_air(air_transfer) //Burn it based on transfered gas target.hotspot_expose((ptank.air_contents.temperature*2) + 380,500) diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm index a5a2398fe0..2e8074f656 100644 --- a/code/game/objects/items/tanks/jetpack.dm +++ b/code/game/objects/items/tanks/jetpack.dm @@ -17,8 +17,7 @@ /obj/item/tank/jetpack/New() ..() if(gas_type) - air_contents.assert_gas(gas_type) - air_contents.gases[gas_type][MOLES] = (6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C) + air_contents.gases[gas_type] = (6 * ONE_ATMOSPHERE) * volume / (R_IDEAL_GAS_EQUATION * T20C) ion_trail = new ion_trail.set_up(src) diff --git a/code/game/objects/items/tanks/tank_types.dm b/code/game/objects/items/tanks/tank_types.dm index f4eb763847..c16762467e 100644 --- a/code/game/objects/items/tanks/tank_types.dm +++ b/code/game/objects/items/tanks/tank_types.dm @@ -21,8 +21,7 @@ /obj/item/tank/internals/oxygen/New() ..() - air_contents.assert_gas(/datum/gas/oxygen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.gases[/datum/gas/oxygen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) return @@ -49,9 +48,8 @@ /obj/item/tank/internals/anesthetic/New() ..() - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide) - air_contents.gases[/datum/gas/oxygen][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + air_contents.gases[/datum/gas/oxygen] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + air_contents.gases[/datum/gas/nitrous_oxide] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD return /* @@ -67,9 +65,8 @@ /obj/item/tank/internals/air/New() ..() - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.gases[/datum/gas/nitrogen][MOLES] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + air_contents.gases[/datum/gas/oxygen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + air_contents.gases[/datum/gas/nitrogen] = (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD return @@ -87,8 +84,7 @@ /obj/item/tank/internals/plasma/New() ..() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.gases[/datum/gas/plasma] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) return /obj/item/tank/internals/plasma/attackby(obj/item/W, mob/user, params) @@ -106,7 +102,7 @@ /obj/item/tank/internals/plasma/full/New() ..() // Plasma asserted in parent - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) return @@ -124,13 +120,12 @@ /obj/item/tank/internals/plasmaman/New() ..() - air_contents.assert_gas(/datum/gas/plasma) - air_contents.gases[/datum/gas/plasma][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.gases[/datum/gas/plasma] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) return /obj/item/tank/internals/plasmaman/full/New() ..() // Plasma asserted in parent - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) return @@ -144,7 +139,7 @@ /obj/item/tank/internals/plasmaman/belt/full/New() ..() // Plasma asserted in parent - air_contents.gases[/datum/gas/plasma][MOLES] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.gases[/datum/gas/plasma] = (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) return @@ -166,8 +161,7 @@ /obj/item/tank/internals/emergency_oxygen/New() ..() - air_contents.assert_gas(/datum/gas/oxygen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) + air_contents.gases[/datum/gas/oxygen] = (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) return /obj/item/tank/internals/emergency_oxygen/engi diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm index 6015cf9f4a..c386726f34 100644 --- a/code/game/objects/structures/transit_tubes/station.dm +++ b/code/game/objects/structures/transit_tubes/station.dm @@ -152,8 +152,8 @@ pod_moving = 0 if(!QDELETED(pod)) var/datum/gas_mixture/floor_mixture = loc.return_air() - floor_mixture.archive() - pod.air_contents.archive() + ARCHIVE_TEMPERATURE(floor_mixture) + ARCHIVE_TEMPERATURE(pod.air_contents) pod.air_contents.share(floor_mixture, 1) //mix the pod's gas mixture with the tile it's on air_update_turf() diff --git a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm index 03082cd4ba..ee46538be1 100644 --- a/code/game/objects/structures/transit_tubes/transit_tube_pod.dm +++ b/code/game/objects/structures/transit_tubes/transit_tube_pod.dm @@ -11,9 +11,8 @@ /obj/structure/transit_tube_pod/Initialize() . = ..() - air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - air_contents.gases[/datum/gas/oxygen][MOLES] = MOLES_O2STANDARD - air_contents.gases[/datum/gas/nitrogen][MOLES] = MOLES_N2STANDARD + air_contents.gases[/datum/gas/oxygen] = MOLES_O2STANDARD + air_contents.gases[/datum/gas/nitrogen] = MOLES_N2STANDARD air_contents.temperature = T20C diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index f108a536d2..e441ccd6cc 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -297,15 +297,14 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( continue var/list/S_gases = S.air.gases for(var/id in S_gases) - ASSERT_GAS(id, total) - total_gases[id][MOLES] += S_gases[id][MOLES] + total_gases[id] += S_gases[id] total.temperature += S.air.temperature air.copy_from(total) var/list/air_gases = air.gases for(var/id in air_gases) - air_gases[id][MOLES] /= turf_count //Averages contents of the turfs, ignoring walls and the like + air_gases[id] /= turf_count //Averages contents of the turfs, ignoring walls and the like air.temperature /= turf_count SSair.add_to_active(src) diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm index fed922c7b4..cff219c63e 100644 --- a/code/game/turfs/open.dm +++ b/code/game/turfs/open.dm @@ -314,9 +314,8 @@ /turf/open/rad_act(pulse_strength) . = ..() if (air.gases[/datum/gas/carbon_dioxide] && air.gases[/datum/gas/oxygen]) - pulse_strength = min(pulse_strength,air.gases[/datum/gas/carbon_dioxide][MOLES]*1000,air.gases[/datum/gas/oxygen][MOLES]*2000) //Ensures matter is conserved properly - air.gases[/datum/gas/carbon_dioxide][MOLES]=max(air.gases[/datum/gas/carbon_dioxide][MOLES]-(pulse_strength/1000),0) - air.gases[/datum/gas/oxygen][MOLES]=max(air.gases[/datum/gas/oxygen][MOLES]-(pulse_strength/2000),0) - air.assert_gas(/datum/gas/pluoxium) - air.gases[/datum/gas/pluoxium][MOLES]+=(pulse_strength/4000) - air.garbage_collect() + pulse_strength = min(pulse_strength,air.gases[/datum/gas/carbon_dioxide]*1000,air.gases[/datum/gas/oxygen]*2000) //Ensures matter is conserved properly + air.gases[/datum/gas/carbon_dioxide]=max(air.gases[/datum/gas/carbon_dioxide]-(pulse_strength/1000),0) + air.gases[/datum/gas/oxygen]=max(air.gases[/datum/gas/oxygen]-(pulse_strength/2000),0) + air.gases[/datum/gas/pluoxium]+=(pulse_strength/4000) + GAS_GARBAGE_COLLECT(air.gases) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 67b33afcc6..57dc357859 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -841,8 +841,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) if(Rad.anchored) if(!Rad.loaded_tank) var/obj/item/tank/internals/plasma/Plasma = new/obj/item/tank/internals/plasma(Rad) - Plasma.air_contents.assert_gas(/datum/gas/plasma) - Plasma.air_contents.gases[/datum/gas/plasma][MOLES] = 70 + Plasma.air_contents.gases[/datum/gas/plasma] = 70 Rad.drainratio = 0 Rad.loaded_tank = Plasma Plasma.forceMove(Rad) diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index 39aff33953..b19b2a91d8 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -9,10 +9,9 @@ var/list/lines = list("[AREACOORD(target)]: [env.temperature] K ([env.temperature - T0C] C), [env.return_pressure()] kPa[(burning)?(", burning"):(null)]") for(var/id in env_gases) - var/gas = env_gases[id] - var/moles = gas[MOLES] + var/moles = env_gases[id] if (moles >= 0.00001) - lines += "[gas[GAS_META][META_GAS_NAME]]: [moles] mol" + lines += "[GLOB.meta_gas_names[id]]: [moles] mol" to_chat(usr, lines.Join("\n")) /client/proc/air_status(turf/target) diff --git a/code/modules/assembly/bomb.dm b/code/modules/assembly/bomb.dm index d40666babb..8ec93f978f 100644 --- a/code/modules/assembly/bomb.dm +++ b/code/modules/assembly/bomb.dm @@ -1,203 +1,202 @@ -/obj/item/onetankbomb - name = "bomb" - icon = 'icons/obj/tank.dmi' - item_state = "assembly" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - throwforce = 5 - w_class = WEIGHT_CLASS_NORMAL - throw_speed = 2 - throw_range = 4 - flags_1 = CONDUCT_1 - var/status = FALSE //0 - not readied //1 - bomb finished with welder - var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device - var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank - -/obj/item/onetankbomb/IsSpecialAssembly() - return TRUE - -/obj/item/onetankbomb/examine(mob/user) - bombtank.examine(user) - -/obj/item/onetankbomb/update_icon() - cut_overlays() - if(bombtank) - icon = bombtank.icon - icon_state = bombtank.icon_state - if(bombassembly) - add_overlay(bombassembly.icon_state) - copy_overlays(bombassembly) - add_overlay("bomb_assembly") - -/obj/item/onetankbomb/wrench_act(mob/living/user, obj/item/I) - to_chat(user, "You disassemble [src]!") - if(bombassembly) - bombassembly.forceMove(drop_location()) - bombassembly.master = null - bombassembly = null - if(bombtank) - bombtank.forceMove(drop_location()) - bombtank.master = null - bombtank = null - qdel(src) - return TRUE - -/obj/item/onetankbomb/welder_act(mob/living/user, obj/item/I) - . = FALSE - if(status) - to_chat(user, "[bombtank] already has a pressure hole!") - return - if(!I.tool_start_check(user, amount=0)) - return - if(I.use_tool(src, user, 0, volume=40)) - status = TRUE - GLOB.bombers += "[key_name(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]" - message_admins("[ADMIN_LOOKUPFLW(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]") - to_chat(user, "A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited.") - add_fingerprint(user) - return TRUE - - -/obj/item/onetankbomb/analyzer_act(mob/living/user, obj/item/I) - bombtank.analyzer_act(user, I) - -/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly - bombassembly.attack_self(user, TRUE) - add_fingerprint(user) - return - -/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. - audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*") - playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) - sleep(10) - if(QDELETED(src)) - return - if(status) - bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file - else - bombtank.release() - -//Assembly / attached device memes - -/obj/item/onetankbomb/Crossed(atom/movable/AM as mob|obj) //for mousetraps - . = ..() - if(bombassembly) - bombassembly.Crossed(AM) - -/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps - if(bombassembly) - bombassembly.on_found(finder) - -/obj/item/onetankbomb/attack_hand() //also for mousetraps - . = ..() - if(.) - return - if(bombassembly) - bombassembly.attack_hand() - -/obj/item/onetankbomb/Move() - . = ..() - if(bombassembly) - bombassembly.setDir(dir) - bombassembly.Move() - -/obj/item/onetankbomb/dropped() - . = ..() - if(bombassembly) - bombassembly.dropped() - - - - -// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ---------- - -//Bomb assembly proc. This turns assembly+tank into a bomb -/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user) - //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it - if(isigniter(assembly.a_left) == isigniter(assembly.a_right)) - return - - if((src in user.get_equipped_items(TRUE)) && !user.canUnEquip(src)) - to_chat(user, "[src] is stuck to you!") - return - - if(!user.canUnEquip(assembly)) - to_chat(user, "[assembly] is stuck to your hand!") - return - - var/obj/item/onetankbomb/bomb = new - user.transferItemToLoc(src, bomb) - user.transferItemToLoc(assembly, bomb) - - bomb.bombassembly = assembly //Tell the bomb about its assembly part - assembly.master = bomb //Tell the assembly about its new owner - - bomb.bombtank = src //Same for tank - master = bomb - - forceMove(bomb) - bomb.update_icon() - - user.put_in_hands(bomb) //Equips the bomb if possible, or puts it on the floor. - to_chat(user, "You attach [assembly] to [src].") - return - -/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode - air_contents.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) - var/fuel_moles = air_contents.gases[/datum/gas/plasma][MOLES] + air_contents.gases[/datum/gas/oxygen][MOLES]/6 - air_contents.garbage_collect() - var/datum/gas_mixture/bomb_mixture = air_contents.copy() - var/strength = 1 - - var/turf/ground_zero = get_turf(loc) - - if(master) - qdel(master) - qdel(src) - - if(bomb_mixture.temperature > (T0C + 400)) - strength = (fuel_moles/15) - - if(strength >=1) - explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1)) - else if(strength >=0.5) - explosion(ground_zero, 0, 1, 2, 4) - else if(strength >=0.2) - explosion(ground_zero, -1, 0, 1, 2) - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - else if(bomb_mixture.temperature > (T0C + 250)) - strength = (fuel_moles/20) - - if(strength >=1) - explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1)) - else if (strength >=0.5) - explosion(ground_zero, -1, 0, 1, 2) - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - else if(bomb_mixture.temperature > (T0C + 100)) - strength = (fuel_moles/25) - - if (strength >=1) - explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1)) - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - else - ground_zero.assume_air(bomb_mixture) - ground_zero.hotspot_expose(1000, 125) - - ground_zero.air_update_turf() - -/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out. - var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles()) - var/turf/T = get_turf(src) - if(!T) - return - T.assume_air(removed) - air_update_turf() +/obj/item/onetankbomb + name = "bomb" + icon = 'icons/obj/tank.dmi' + item_state = "assembly" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + throwforce = 5 + w_class = WEIGHT_CLASS_NORMAL + throw_speed = 2 + throw_range = 4 + flags_1 = CONDUCT_1 + var/status = FALSE //0 - not readied //1 - bomb finished with welder + var/obj/item/assembly_holder/bombassembly = null //The first part of the bomb is an assembly holder, holding an igniter+some device + var/obj/item/tank/bombtank = null //the second part of the bomb is a plasma tank + +/obj/item/onetankbomb/IsSpecialAssembly() + return TRUE + +/obj/item/onetankbomb/examine(mob/user) + bombtank.examine(user) + +/obj/item/onetankbomb/update_icon() + cut_overlays() + if(bombtank) + icon = bombtank.icon + icon_state = bombtank.icon_state + if(bombassembly) + add_overlay(bombassembly.icon_state) + copy_overlays(bombassembly) + add_overlay("bomb_assembly") + +/obj/item/onetankbomb/wrench_act(mob/living/user, obj/item/I) + to_chat(user, "You disassemble [src]!") + if(bombassembly) + bombassembly.forceMove(drop_location()) + bombassembly.master = null + bombassembly = null + if(bombtank) + bombtank.forceMove(drop_location()) + bombtank.master = null + bombtank = null + qdel(src) + return TRUE + +/obj/item/onetankbomb/welder_act(mob/living/user, obj/item/I) + . = FALSE + if(status) + to_chat(user, "[bombtank] already has a pressure hole!") + return + if(!I.tool_start_check(user, amount=0)) + return + if(I.use_tool(src, user, 0, volume=40)) + status = TRUE + GLOB.bombers += "[key_name(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]" + message_admins("[ADMIN_LOOKUPFLW(user)] welded a single tank bomb. Temp: [bombtank.air_contents.temperature-T0C]") + to_chat(user, "A pressure hole has been bored to [bombtank] valve. \The [bombtank] can now be ignited.") + add_fingerprint(user) + return TRUE + + +/obj/item/onetankbomb/analyzer_act(mob/living/user, obj/item/I) + bombtank.analyzer_act(user, I) + +/obj/item/onetankbomb/attack_self(mob/user) //pressing the bomb accesses its assembly + bombassembly.attack_self(user, TRUE) + add_fingerprint(user) + return + +/obj/item/onetankbomb/receive_signal() //This is mainly called by the sensor through sense() to the holder, and from the holder to here. + audible_message("[icon2html(src, hearers(src))] *beep* *beep* *beep*") + playsound(src, 'sound/machines/triple_beep.ogg', ASSEMBLY_BEEP_VOLUME, TRUE) + sleep(10) + if(QDELETED(src)) + return + if(status) + bombtank.ignite() //if its not a dud, boom (or not boom if you made shitty mix) the ignite proc is below, in this file + else + bombtank.release() + +//Assembly / attached device memes + +/obj/item/onetankbomb/Crossed(atom/movable/AM as mob|obj) //for mousetraps + . = ..() + if(bombassembly) + bombassembly.Crossed(AM) + +/obj/item/onetankbomb/on_found(mob/finder) //for mousetraps + if(bombassembly) + bombassembly.on_found(finder) + +/obj/item/onetankbomb/attack_hand() //also for mousetraps + . = ..() + if(.) + return + if(bombassembly) + bombassembly.attack_hand() + +/obj/item/onetankbomb/Move() + . = ..() + if(bombassembly) + bombassembly.setDir(dir) + bombassembly.Move() + +/obj/item/onetankbomb/dropped() + . = ..() + if(bombassembly) + bombassembly.dropped() + + + + +// ---------- Procs below are for tanks that are used exclusively in 1-tank bombs ---------- + +//Bomb assembly proc. This turns assembly+tank into a bomb +/obj/item/tank/proc/bomb_assemble(obj/item/assembly_holder/assembly, mob/living/user) + //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it + if(isigniter(assembly.a_left) == isigniter(assembly.a_right)) + return + + if((src in user.get_equipped_items(TRUE)) && !user.canUnEquip(src)) + to_chat(user, "[src] is stuck to you!") + return + + if(!user.canUnEquip(assembly)) + to_chat(user, "[assembly] is stuck to your hand!") + return + + var/obj/item/onetankbomb/bomb = new + user.transferItemToLoc(src, bomb) + user.transferItemToLoc(assembly, bomb) + + bomb.bombassembly = assembly //Tell the bomb about its assembly part + assembly.master = bomb //Tell the assembly about its new owner + + bomb.bombtank = src //Same for tank + master = bomb + + forceMove(bomb) + bomb.update_icon() + + user.put_in_hands(bomb) //Equips the bomb if possible, or puts it on the floor. + to_chat(user, "You attach [assembly] to [src].") + return + +/obj/item/tank/proc/ignite() //This happens when a bomb is told to explode + var/fuel_moles = air_contents.gases[/datum/gas/plasma] + air_contents.gases[/datum/gas/oxygen]/6 + GAS_GARBAGE_COLLECT(air_contents.gases) + var/datum/gas_mixture/bomb_mixture = air_contents.copy() + var/strength = 1 + + var/turf/ground_zero = get_turf(loc) + + if(master) + qdel(master) + qdel(src) + + if(bomb_mixture.temperature > (T0C + 400)) + strength = (fuel_moles/15) + + if(strength >=1) + explosion(ground_zero, round(strength,1), round(strength*2,1), round(strength*3,1), round(strength*4,1)) + else if(strength >=0.5) + explosion(ground_zero, 0, 1, 2, 4) + else if(strength >=0.2) + explosion(ground_zero, -1, 0, 1, 2) + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + else if(bomb_mixture.temperature > (T0C + 250)) + strength = (fuel_moles/20) + + if(strength >=1) + explosion(ground_zero, 0, round(strength,1), round(strength*2,1), round(strength*3,1)) + else if (strength >=0.5) + explosion(ground_zero, -1, 0, 1, 2) + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + else if(bomb_mixture.temperature > (T0C + 100)) + strength = (fuel_moles/25) + + if (strength >=1) + explosion(ground_zero, -1, 0, round(strength,1), round(strength*3,1)) + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + else + ground_zero.assume_air(bomb_mixture) + ground_zero.hotspot_expose(1000, 125) + + ground_zero.air_update_turf() + +/obj/item/tank/proc/release() //This happens when the bomb is not welded. Tank contents are just spat out. + var/datum/gas_mixture/removed = air_contents.remove(air_contents.total_moles()) + var/turf/T = get_turf(src) + if(!T) + return + T.assume_air(removed) + air_update_turf() diff --git a/code/modules/atmospherics/environmental/LINDA_fire.dm b/code/modules/atmospherics/environmental/LINDA_fire.dm index bf5d8efb13..d1da05e42d 100644 --- a/code/modules/atmospherics/environmental/LINDA_fire.dm +++ b/code/modules/atmospherics/environmental/LINDA_fire.dm @@ -14,9 +14,9 @@ if(!air_contents) return 0 - var/oxy = air_contents.gases[/datum/gas/oxygen] ? air_contents.gases[/datum/gas/oxygen][MOLES] : 0 - var/tox = air_contents.gases[/datum/gas/plasma] ? air_contents.gases[/datum/gas/plasma][MOLES] : 0 - var/trit = air_contents.gases[/datum/gas/tritium] ? air_contents.gases[/datum/gas/tritium][MOLES] : 0 + var/oxy = air_contents.gases[/datum/gas/oxygen] + var/tox = air_contents.gases[/datum/gas/plasma] + var/trit = air_contents.gases[/datum/gas/tritium] if(active_hotspot) if(soh) if((tox > 0.5 || trit > 0.5) && oxy > 0.5) @@ -162,7 +162,7 @@ color = list(LERP(0.3, 1, 1-greyscale_fire) * heat_r,0.3 * heat_g * greyscale_fire,0.3 * heat_b * greyscale_fire, 0.59 * heat_r * greyscale_fire,LERP(0.59, 1, 1-greyscale_fire) * heat_g,0.59 * heat_b * greyscale_fire, 0.11 * heat_r * greyscale_fire,0.11 * heat_g * greyscale_fire,LERP(0.11, 1, 1-greyscale_fire) * heat_b, 0,0,0) alpha = heat_a -#define INSUFFICIENT(path) (!location.air.gases[path] || location.air.gases[path][MOLES] < 0.5) +#define INSUFFICIENT(path) (location.air.gases[path] < 0.5) /obj/effect/hotspot/process() if(just_spawned) just_spawned = FALSE @@ -184,7 +184,7 @@ return //Not enough to burn - if(((!location.air.gases[/datum/gas/plasma] || location.air.gases[/datum/gas/plasma][MOLES] < 0.5) && (!location.air.gases[/datum/gas/tritium] || location.air.gases[/datum/gas/tritium][MOLES] < 0.5)) || location.air.gases[/datum/gas/oxygen][MOLES] < 0.5) + if((location.air.gases[/datum/gas/plasma] < 0.5 && location.air.gases[/datum/gas/tritium] < 0.5) || location.air.gases[/datum/gas/oxygen] < 0.5) qdel(src) return diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm index 45bb47ab58..ea555c9489 100644 --- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm +++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm @@ -88,7 +88,7 @@ temperature_archived = temperature /turf/open/archive() - air.archive() + ARCHIVE_TEMPERATURE(air) archived_cycle = SSair.times_fired temperature_archived = temperature @@ -121,10 +121,9 @@ if (nonoverlaying_gases[id]) continue var/gas = gases[id] - var/gas_meta = gas[GAS_META] - var/gas_overlay = gas_meta[META_GAS_OVERLAY] - if(gas_overlay && gas[MOLES] > gas_meta[META_GAS_MOLES_VISIBLE]) - . += gas_overlay[min(FACTOR_GAS_VISIBLE_MAX, CEILING(gas[MOLES] / MOLES_GAS_VISIBLE_STEP, 1))] + var/gas_overlay = GLOB.meta_gas_overlays[id] + if(gas_overlay && gas > GLOB.meta_gas_visibility[id]) + . += gas_overlay[min(FACTOR_GAS_VISIBLE_MAX, CEILING(gas / MOLES_GAS_VISIBLE_STEP, 1))] /proc/typecache_of_gases_with_no_overlays() . = list() @@ -215,7 +214,7 @@ if (planet_atmos) //share our air with the "atmosphere" "above" the turf var/datum/gas_mixture/G = new G.copy_from_turf(src) - G.archive() + ARCHIVE_TEMPERATURE(G) if(our_air.compare(G)) if(!our_excited_group) var/datum/excited_group/EG = new @@ -232,6 +231,11 @@ atmos_cooldown = cached_atmos_cooldown +/turf/open/space/process_cell(fire_count) //dumb hack to prevent space pollution + . = ..() + var/datum/gas_mixture/immutable/I = space_gas + I.after_process_cell() + /turf/proc/process_cell_reaction() SSair.remove_from_react_queue(src) @@ -327,7 +331,7 @@ A.merge(T.air) for(var/id in A_gases) - A_gases[id][MOLES] /= turflen + A_gases[id] /= turflen for(var/t in turf_list) var/turf/open/T = t diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 0304946111..9858db2abb 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -5,82 +5,38 @@ What are the archived variables for? */ #define MINIMUM_HEAT_CAPACITY 0.0003 #define MINIMUM_MOLE_COUNT 0.01 -#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 */ -GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm -GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) - -/proc/init_gaslist_cache() - . = list() - for(var/id in GLOB.meta_gas_info) - var/list/cached_gas = new(3) - - .[id] = cached_gas - - cached_gas[MOLES] = 0 - cached_gas[ARCHIVE] = 0 - cached_gas[GAS_META] = GLOB.meta_gas_info[id] +//Unomos - global list inits for all of the meta gas lists. +//This setup allows procs to only look at one list instead of trying to dig around in lists-within-lists +GLOBAL_LIST_INIT(meta_gas_specific_heats, meta_gas_heat_list()) +GLOBAL_LIST_INIT(meta_gas_names, meta_gas_name_list()) +GLOBAL_LIST_INIT(meta_gas_visibility, meta_gas_visibility_list()) +GLOBAL_LIST_INIT(meta_gas_overlays, meta_gas_overlay_list()) +GLOBAL_LIST_INIT(meta_gas_dangers, meta_gas_danger_list()) +GLOBAL_LIST_INIT(meta_gas_ids, meta_gas_id_list()) +GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list()) /datum/gas_mixture - var/list/gases + var/list/gases = list() var/temperature = 0 //kelvins var/tmp/temperature_archived = 0 var/volume = CELL_VOLUME //liters var/last_share = 0 - var/list/reaction_results + var/list/reaction_results = list() var/list/analyzer_results //used for analyzer feedback - not initialized until its used var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures /datum/gas_mixture/New(volume) - gases = new if (!isnull(volume)) src.volume = volume - reaction_results = new - -//listmos procs -//use the macros in performance intensive areas. for their definitions, refer to code/__DEFINES/atmospherics.dm - - //assert_gas(gas_id) - used to guarantee that the gas list for this id exists in gas_mixture.gases. - //Must be used before adding to a gas. May be used before reading from a gas. -/datum/gas_mixture/proc/assert_gas(gas_id) - ASSERT_GAS(gas_id, src) - - //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, src) - - //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) - ADD_GAS(gas_id, gases) - - //add_gases(args) - shorthand for calling add_gas() once for each gas_type. -/datum/gas_mixture/proc/add_gases() - var/cached_gases = gases - for(var/id in args) - ADD_GAS(id, cached_gases) - - //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(QUANTIZE(cached_gases[id][MOLES]) <= 0 && QUANTIZE(cached_gases[id][ARCHIVE]) <= 0) - cached_gases -= id //PV = nRT -/datum/gas_mixture/proc/heat_capacity(data = MOLES) //joules per kelvin +/datum/gas_mixture/proc/heat_capacity() //joules per kelvin var/list/cached_gases = gases + var/list/cached_gasheats = GLOB.meta_gas_specific_heats . = 0 for(var/id in cached_gases) - var/gas_data = cached_gases[id] - . += gas_data[data] * gas_data[GAS_META][META_GAS_SPECIFIC_HEAT] + . += cached_gases[id] * cached_gasheats[id] /datum/gas_mixture/turf/heat_capacity() . = ..() @@ -108,10 +64,6 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) /datum/gas_mixture/proc/thermal_energy() //joules return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas -/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 @@ -156,14 +108,6 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) //Performs various reactions such as combustion or fusion (LOL) //Returns: 1 if any reaction took place; 0 otherwise -/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) @@ -181,8 +125,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/list/giver_gases = giver.gases //gas transfer for(var/giver_id in giver_gases) - ASSERT_GAS(giver_id, src) - cached_gases[giver_id][MOLES] += giver_gases[giver_id][MOLES] + cached_gases[giver_id] += giver_gases[giver_id] return 1 @@ -198,10 +141,9 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) removed.temperature = temperature for(var/id in cached_gases) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE((cached_gases[id][MOLES] / sum) * amount) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] - garbage_collect() + removed_gases[id] = QUANTIZE((cached_gases[id] / sum) * amount) + cached_gases[id] -= removed_gases[id] + GAS_GARBAGE_COLLECT(gases) return removed @@ -216,11 +158,10 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) removed.temperature = temperature for(var/id in cached_gases) - ADD_GAS(id, removed.gases) - removed_gases[id][MOLES] = QUANTIZE(cached_gases[id][MOLES] * ratio) - cached_gases[id][MOLES] -= removed_gases[id][MOLES] + removed_gases[id] = QUANTIZE(cached_gases[id] * ratio) + cached_gases[id] -= removed_gases[id] - garbage_collect() + GAS_GARBAGE_COLLECT(gases) return removed @@ -231,8 +172,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) copy.temperature = temperature for(var/id in cached_gases) - ADD_GAS(id, copy.gases) - copy_gases[id][MOLES] = cached_gases[id][MOLES] + copy_gases[id] = cached_gases[id] return copy @@ -243,8 +183,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) temperature = sample.temperature for(var/id in sample_gases) - ASSERT_GAS(id,src) - cached_gases[id][MOLES] = sample_gases[id][MOLES] + cached_gases[id] = sample_gases[id] //remove all gases not in the sample cached_gases &= sample_gases @@ -272,8 +211,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/path = id if(!ispath(path)) path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around - ADD_GAS(path, gases) - gases[path][MOLES] = text2num(gas[id]) + gases[path] = text2num(gas[id]) return 1 /datum/gas_mixture/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) @@ -296,26 +234,25 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) var/moved_moles = 0 var/abs_moved_moles = 0 + //we're gonna define these vars outside of this for loop because as it turns out, var declaration is pricy + var/delta + var/gas_heat_capacity + //and also cache this shit rq because that results in sanic speed for reasons byond explanation + var/list/cached_gasheats = GLOB.meta_gas_specific_heats //GAS TRANSFER - for(var/id in sharer_gases - cached_gases) // create gases not in our cache - ADD_GAS(id, gases) - for(var/id in cached_gases) // transfer gases - ASSERT_GAS(id, sharer) + for(var/id in cached_gases | sharer_gases) // transfer gases - 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 + delta = QUANTIZE(cached_gases[id] - sharer_gases[id])/(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] + gas_heat_capacity = delta * cached_gasheats[id] 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. - gas[MOLES] -= delta - sharergas[MOLES] += delta + cached_gases[id] -= delta + sharer_gases[id] += delta moved_moles += delta abs_moved_moles += abs(delta) @@ -338,11 +275,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT) - if(length(cached_gases ^ sharer_gases)) //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 if (initial(sharer.gc_share)) - sharer.garbage_collect() + GAS_GARBAGE_COLLECT(sharer.gases) if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE) var/our_moles TOTAL_MOLES(cached_gases,our_moles) @@ -356,8 +290,8 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) 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(ARCHIVE) - sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity(ARCHIVE) + var/self_heat_capacity = heat_capacity() + sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity() if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY)) var/heat = conduction_coefficient*temperature_delta* \ @@ -376,9 +310,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) for(var/id in cached_gases | sample_gases) // compare gases from either mixture var/gas_moles = cached_gases[id] - gas_moles = gas_moles ? gas_moles[MOLES] : 0 var/sample_moles = sample_gases[id] - sample_moles = sample_moles ? sample_moles[MOLES] : 0 var/delta = abs(gas_moles - sample_moles) if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \ delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE) @@ -425,7 +357,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) for(var/id in min_reqs) if (id == "TEMP" || id == "ENER") continue - if(!cached_gases[id] || cached_gases[id][MOLES] < min_reqs[id]) + if(cached_gases[id] < min_reqs[id]) continue reaction_loop //at this point, all minimum requirements for the reaction are satisfied. @@ -449,7 +381,7 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) if (. & STOP_REACTIONS) break if(.) - garbage_collect() + GAS_GARBAGE_COLLECT(gases) if(temperature < TCMB) //just for safety temperature = TCMB diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm index 9dcf8c3145..d628826b01 100644 --- a/code/modules/atmospherics/gasmixtures/gas_types.dm +++ b/code/modules/atmospherics/gasmixtures/gas_types.dm @@ -1,33 +1,66 @@ GLOBAL_LIST_INIT(hardcoded_gases, list(/datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/plasma)) //the main four gases, which were at one time hardcoded GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/carbon_dioxide, /datum/gas/pluoxium, /datum/gas/stimulum, /datum/gas/nitryl))) //unable to react amongst themselves -/proc/meta_gas_list() - . = subtypesof(/datum/gas) - for(var/gas_path in .) - var/list/gas_info = new(7) - 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 /list(FACTOR_GAS_VISIBLE_MAX) - for(var/i in 1 to FACTOR_GAS_VISIBLE_MAX) - gas_info[META_GAS_OVERLAY][i] = new /obj/effect/overlay/gas(initial(gas.gas_overlay), i * 255 / FACTOR_GAS_VISIBLE_MAX) - gas_info[META_GAS_FUSION_POWER] = initial(gas.fusion_power) - gas_info[META_GAS_DANGER] = initial(gas.dangerous) - gas_info[META_GAS_ID] = initial(gas.id) - .[gas_path] = gas_info - /proc/gas_id2path(id) - var/list/meta_gas = GLOB.meta_gas_info + var/list/meta_gas = GLOB.meta_gas_ids if(id in meta_gas) return id for(var/path in meta_gas) - if(meta_gas[path][META_GAS_ID] == id) + if(meta_gas[path] == id) return path return "" +//Unomos - oh god oh fuck oh shit oh lord have mercy this is messy as fuck oh god +//my addiction to seeing better performance numbers isn't healthy, kids +//you see this shit, children? +//i am not a good idol. don't take after me. +//this is literally worse than my alcohol addiction +/proc/meta_gas_heat_list() + . = subtypesof(/datum/gas) + for(var/gas_path in .) + var/datum/gas/gas = gas_path + .[gas_path] = initial(gas.specific_heat) + +/proc/meta_gas_name_list() + . = subtypesof(/datum/gas) + for(var/gas_path in .) + var/datum/gas/gas = gas_path + .[gas_path] = initial(gas.name) + +/proc/meta_gas_visibility_list() + . = subtypesof(/datum/gas) + for(var/gas_path in .) + var/datum/gas/gas = gas_path + .[gas_path] = initial(gas.moles_visible) + +/proc/meta_gas_overlay_list() + . = subtypesof(/datum/gas) + for(var/gas_path in .) + var/datum/gas/gas = gas_path + .[gas_path] = 0 //gotta make sure if(GLOB.meta_gas_overlays[gaspath]) doesn't break + if(initial(gas.moles_visible) != null) + .[gas_path] = new /list(FACTOR_GAS_VISIBLE_MAX) + for(var/i in 1 to FACTOR_GAS_VISIBLE_MAX) + .[gas_path][i] = new /obj/effect/overlay/gas(initial(gas.gas_overlay), i * 255 / FACTOR_GAS_VISIBLE_MAX) + +/proc/meta_gas_danger_list() + . = subtypesof(/datum/gas) + for(var/gas_path in .) + var/datum/gas/gas = gas_path + .[gas_path] = initial(gas.dangerous) + +/proc/meta_gas_id_list() + . = subtypesof(/datum/gas) + for(var/gas_path in .) + var/datum/gas/gas = gas_path + .[gas_path] = initial(gas.id) + +/proc/meta_gas_fusion_list() + . = subtypesof(/datum/gas) + for(var/gas_path in .) + var/datum/gas/gas = gas_path + .[gas_path] = initial(gas.fusion_power) + /*||||||||||||||/----------\||||||||||||||*\ ||||||||||||||||[GAS DATUMS]|||||||||||||||| ||||||||||||||||\__________/|||||||||||||||| diff --git a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm index 08f64b5f4a..53f7ede3e6 100644 --- a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm +++ b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm @@ -7,22 +7,18 @@ /datum/gas_mixture/immutable/New() ..() - garbage_collect() - -/datum/gas_mixture/immutable/garbage_collect() temperature = initial_temperature temperature_archived = initial_temperature gases.Cut() -/datum/gas_mixture/immutable/archive() - return 1 //nothing changes, so we do nothing and the archive is successful - /datum/gas_mixture/immutable/merge() return 0 //we're immutable. /datum/gas_mixture/immutable/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) . = ..(sharer, 0) - garbage_collect() + temperature = initial_temperature + temperature_archived = initial_temperature + gases.Cut() /datum/gas_mixture/immutable/react() return 0 //we're immutable. @@ -43,6 +39,10 @@ . = ..() temperature = initial_temperature +/datum/gas_mixture/immutable/proc/after_process_cell() + temperature = initial_temperature + temperature_archived = initial_temperature + gases.Cut() //used by space tiles /datum/gas_mixture/immutable/space @@ -62,10 +62,13 @@ /datum/gas_mixture/immutable/cloner initial_temperature = T20C -/datum/gas_mixture/immutable/cloner/garbage_collect() +/datum/gas_mixture/immutable/cloner/New() ..() - ADD_GAS(/datum/gas/nitrogen, gases) - gases[/datum/gas/nitrogen][MOLES] = MOLES_O2STANDARD + MOLES_N2STANDARD + gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD + +/datum/gas_mixture/immutable/cloner/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4) + . = ..(sharer, 0) + gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD /datum/gas_mixture/immutable/cloner/heat_capacity() return (MOLES_O2STANDARD + MOLES_N2STANDARD)*20 //specific heat of nitrogen is 20 diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 84ad1e9258..46ad4f26b7 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -60,7 +60,7 @@ if(location && location.freon_gas_act()) . = REACTING else if(location && location.water_vapor_gas_act()) - air.gases[/datum/gas/water_vapor][MOLES] -= MOLES_GAS_VISIBLE + air.gases[/datum/gas/water_vapor] -= MOLES_GAS_VISIBLE . = REACTING //tritium combustion: combustion of oxygen and tritium (treated as hydrocarbons). creates hotspots. exothermic @@ -86,21 +86,20 @@ var/turf/open/location = isturf(holder) ? holder : null var/burned_fuel = 0 - if(cached_gases[/datum/gas/oxygen][MOLES] < cached_gases[/datum/gas/tritium][MOLES]) - burned_fuel = cached_gases[/datum/gas/oxygen][MOLES]/TRITIUM_BURN_OXY_FACTOR - cached_gases[/datum/gas/tritium][MOLES] -= burned_fuel + if(cached_gases[/datum/gas/oxygen] < cached_gases[/datum/gas/tritium]) + burned_fuel = cached_gases[/datum/gas/oxygen]/TRITIUM_BURN_OXY_FACTOR + cached_gases[/datum/gas/tritium] -= burned_fuel else - burned_fuel = cached_gases[/datum/gas/tritium][MOLES]*TRITIUM_BURN_TRIT_FACTOR - cached_gases[/datum/gas/tritium][MOLES] -= cached_gases[/datum/gas/tritium][MOLES]/TRITIUM_BURN_TRIT_FACTOR - cached_gases[/datum/gas/oxygen][MOLES] -= cached_gases[/datum/gas/tritium][MOLES] + burned_fuel = cached_gases[/datum/gas/tritium]*TRITIUM_BURN_TRIT_FACTOR + cached_gases[/datum/gas/tritium] -= cached_gases[/datum/gas/tritium]/TRITIUM_BURN_TRIT_FACTOR + cached_gases[/datum/gas/oxygen] -= cached_gases[/datum/gas/tritium] if(burned_fuel) energy_released += FIRE_HYDROGEN_ENERGY_RELEASED * burned_fuel if(location && prob(10) && burned_fuel > TRITIUM_MINIMUM_RADIATION_ENERGY) //woah there let's not crash the server radiation_pulse(location, energy_released/TRITIUM_BURN_RADIOACTIVITY_FACTOR) - ASSERT_GAS(/datum/gas/water_vapor, air) //oxygen+more-or-less hydrogen=H2O - cached_gases[/datum/gas/water_vapor][MOLES] += burned_fuel/TRITIUM_BURN_OXY_FACTOR + cached_gases[/datum/gas/water_vapor] += burned_fuel/TRITIUM_BURN_OXY_FACTOR cached_results["fire"] += burned_fuel @@ -157,23 +156,21 @@ temperature_scale = (temperature-PLASMA_MINIMUM_BURN_TEMPERATURE)/(PLASMA_UPPER_TEMPERATURE-PLASMA_MINIMUM_BURN_TEMPERATURE) if(temperature_scale > 0) oxygen_burn_rate = OXYGEN_BURN_RATE_BASE - temperature_scale - if(cached_gases[/datum/gas/oxygen][MOLES] / cached_gases[/datum/gas/plasma][MOLES] > SUPER_SATURATION_THRESHOLD) //supersaturation. Form Tritium. + if(cached_gases[/datum/gas/oxygen] / cached_gases[/datum/gas/plasma] > SUPER_SATURATION_THRESHOLD) //supersaturation. Form Tritium. super_saturation = TRUE - if(cached_gases[/datum/gas/oxygen][MOLES] > cached_gases[/datum/gas/plasma][MOLES]*PLASMA_OXYGEN_FULLBURN) - plasma_burn_rate = (cached_gases[/datum/gas/plasma][MOLES]*temperature_scale)/PLASMA_BURN_RATE_DELTA + if(cached_gases[/datum/gas/oxygen] > cached_gases[/datum/gas/plasma]*PLASMA_OXYGEN_FULLBURN) + plasma_burn_rate = (cached_gases[/datum/gas/plasma]*temperature_scale)/PLASMA_BURN_RATE_DELTA else - plasma_burn_rate = (temperature_scale*(cached_gases[/datum/gas/oxygen][MOLES]/PLASMA_OXYGEN_FULLBURN))/PLASMA_BURN_RATE_DELTA + plasma_burn_rate = (temperature_scale*(cached_gases[/datum/gas/oxygen]/PLASMA_OXYGEN_FULLBURN))/PLASMA_BURN_RATE_DELTA if(plasma_burn_rate > MINIMUM_HEAT_CAPACITY) - plasma_burn_rate = min(plasma_burn_rate,cached_gases[/datum/gas/plasma][MOLES],cached_gases[/datum/gas/oxygen][MOLES]/oxygen_burn_rate) //Ensures matter is conserved properly - cached_gases[/datum/gas/plasma][MOLES] = QUANTIZE(cached_gases[/datum/gas/plasma][MOLES] - plasma_burn_rate) - cached_gases[/datum/gas/oxygen][MOLES] = QUANTIZE(cached_gases[/datum/gas/oxygen][MOLES] - (plasma_burn_rate * oxygen_burn_rate)) + plasma_burn_rate = min(plasma_burn_rate,cached_gases[/datum/gas/plasma],cached_gases[/datum/gas/oxygen]/oxygen_burn_rate) //Ensures matter is conserved properly + cached_gases[/datum/gas/plasma] = QUANTIZE(cached_gases[/datum/gas/plasma] - plasma_burn_rate) + cached_gases[/datum/gas/oxygen] = QUANTIZE(cached_gases[/datum/gas/oxygen] - (plasma_burn_rate * oxygen_burn_rate)) if (super_saturation) - ASSERT_GAS(/datum/gas/tritium,air) - cached_gases[/datum/gas/tritium][MOLES] += plasma_burn_rate + cached_gases[/datum/gas/tritium] += plasma_burn_rate else - ASSERT_GAS(/datum/gas/carbon_dioxide,air) - cached_gases[/datum/gas/carbon_dioxide][MOLES] += plasma_burn_rate + cached_gases[/datum/gas/carbon_dioxide] += plasma_burn_rate energy_released += FIRE_PLASMA_ENERGY_RELEASED * (plasma_burn_rate) @@ -231,21 +228,21 @@ var/old_heat_capacity = air.heat_capacity() var/reaction_energy = 0 - var/mediation = FUSION_MEDIATION_FACTOR*(air.heat_capacity()-(cached_gases[/datum/gas/plasma][MOLES]*cached_gases[/datum/gas/plasma][GAS_META][META_GAS_SPECIFIC_HEAT]))/(air.total_moles()-cached_gases[/datum/gas/plasma][MOLES]) //This is the average specific heat of the mixture,not including plasma. + var/mediation = FUSION_MEDIATION_FACTOR*(air.heat_capacity()-(cached_gases[/datum/gas/plasma]*GLOB.meta_gas_specific_heats[/datum/gas/plasma]))/(air.total_moles()-cached_gases[/datum/gas/plasma]) //This is the average specific heat of the mixture,not including plasma. - var/gases_fused = air.total_moles() - cached_gases[/datum/gas/plasma][MOLES] - var/plasma_differential = (cached_gases[/datum/gas/plasma][MOLES] - gases_fused) / air.total_moles() + var/gases_fused = air.total_moles() - cached_gases[/datum/gas/plasma] + var/plasma_differential = (cached_gases[/datum/gas/plasma] - gases_fused) / air.total_moles() var/reaction_efficiency = FUSION_EFFICIENCY_BASE ** -((plasma_differential ** 2) / FUSION_EFFICIENCY_DIVISOR) //https://www.desmos.com/calculator/6jjx3vdrvx var/gas_power = 0 for (var/gas_id in cached_gases) - gas_power += reaction_efficiency * (cached_gases[gas_id][GAS_META][META_GAS_FUSION_POWER]*cached_gases[gas_id][MOLES]) + gas_power += reaction_efficiency * (GLOB.meta_gas_fusions[gas_id]*cached_gases[gas_id]) var/power_ratio = gas_power/mediation cached_scan_results[id] = power_ratio //used for analyzer feedback for (var/gas_id in cached_gases) //and now we fuse - cached_gases[gas_id][MOLES] = 0 + cached_gases[gas_id] = 0 var/radiation_power = (FUSION_RADIATION_FACTOR * power_ratio) / (power_ratio + FUSION_RADIATION_CONSTANT) //https://www.desmos.com/calculator/4i1f296phl var/zap_power = ((FUSION_ZAP_POWER_ASYMPTOTE * power_ratio) / (power_ratio + FUSION_ZAP_POWER_CONSTANT)) + FUSION_ZAP_POWER_BASE //https://www.desmos.com/calculator/n0zkdpxnrr @@ -255,33 +252,30 @@ if (power_ratio > FUSION_SUPER_TIER_THRESHOLD) //power ratio 50+: SUPER TIER. The gases become so energized that they fuse into a ton of tritium, which is pretty nice! Until you consider the fact that everything just exploded, the canister is probably going to break and you're irradiated. reaction_energy += gases_fused * FUSION_RELEASE_ENERGY_SUPER * (power_ratio / FUSION_ENERGY_DIVISOR_SUPER) - cached_gases[/datum/gas/tritium][MOLES] += gases_fused * FUSION_GAS_CREATION_FACTOR_TRITIUM //60% of the gas is converted to energy, 40% to trit + cached_gases[/datum/gas/tritium] += gases_fused * FUSION_GAS_CREATION_FACTOR_TRITIUM //60% of the gas is converted to energy, 40% to trit fusion_prepare_to_die_edition_rng = 100 //Wait a minute.. do_explosion = TRUE zap_range = FUSION_ZAP_RANGE_SUPER else if (power_ratio > FUSION_HIGH_TIER_THRESHOLD) //power ratio 20-50; High tier. The reaction is so energized that it fuses into a small amount of stimulum, and some pluoxium. Very dangerous, but super cool and super useful. reaction_energy += gases_fused * FUSION_RELEASE_ENERGY_HIGH * (power_ratio / FUSION_ENERGY_DIVISOR_HIGH) - air.assert_gases(/datum/gas/stimulum, /datum/gas/pluoxium) - cached_gases[/datum/gas/stimulum][MOLES] += gases_fused * FUSION_GAS_CREATION_FACTOR_STIM //40% of the gas is converted to energy, 60% to stim and pluox - cached_gases[/datum/gas/pluoxium][MOLES] += gases_fused * FUSION_GAS_CREATION_FACTOR_PLUOX + cached_gases[/datum/gas/stimulum] += gases_fused * FUSION_GAS_CREATION_FACTOR_STIM //40% of the gas is converted to energy, 60% to stim and pluox + cached_gases[/datum/gas/pluoxium] += gases_fused * FUSION_GAS_CREATION_FACTOR_PLUOX fusion_prepare_to_die_edition_rng = power_ratio //Now we're getting into dangerous territory do_explosion = TRUE zap_range = FUSION_ZAP_RANGE_HIGH else if (power_ratio > FUSION_MID_TIER_THRESHOLD) //power_ratio 5 to 20; Mediation is overpowered, fusion reaction starts to break down. reaction_energy += gases_fused * FUSION_RELEASE_ENERGY_MID * (power_ratio / FUSION_ENERGY_DIVISOR_MID) - air.assert_gases(/datum/gas/nitryl,/datum/gas/nitrous_oxide) - cached_gases[/datum/gas/nitryl][MOLES] += gases_fused * FUSION_GAS_CREATION_FACTOR_NITRYL //20% of the gas is converted to energy, 80% to nitryl and N2O - cached_gases[/datum/gas/nitrous_oxide][MOLES] += gases_fused * FUSION_GAS_CREATION_FACTOR_N2O + cached_gases[/datum/gas/nitryl] += gases_fused * FUSION_GAS_CREATION_FACTOR_NITRYL //20% of the gas is converted to energy, 80% to nitryl and N2O + cached_gases[/datum/gas/nitrous_oxide] += gases_fused * FUSION_GAS_CREATION_FACTOR_N2O fusion_prepare_to_die_edition_rng = power_ratio * FUSION_MID_TIER_RAD_PROB_FACTOR //Still unlikely, but don't stand next to the reaction unprotected zap_range = FUSION_ZAP_RANGE_MID else //power ratio 0 to 5; Gas power is overpowered. Fusion isn't nearly as powerful. reaction_energy += gases_fused * FUSION_RELEASE_ENERGY_LOW * (power_ratio / FUSION_ENERGY_DIVISOR_LOW) - air.assert_gases(/datum/gas/bz, /datum/gas/carbon_dioxide) - cached_gases[/datum/gas/bz][MOLES] += gases_fused * FUSION_GAS_CREATION_FACTOR_BZ //10% of the gas is converted to energy, 90% to BZ and CO2 - cached_gases[/datum/gas/carbon_dioxide][MOLES] += gases_fused * FUSION_GAS_CREATION_FACTOR_CO2 + cached_gases[/datum/gas/bz] += gases_fused * FUSION_GAS_CREATION_FACTOR_BZ //10% of the gas is converted to energy, 90% to BZ and CO2 + cached_gases[/datum/gas/carbon_dioxide] += gases_fused * FUSION_GAS_CREATION_FACTOR_CO2 fusion_prepare_to_die_edition_rng = power_ratio * FUSION_LOW_TIER_RAD_PROB_FACTOR //Low, but still something to look out for zap_range = FUSION_ZAP_RANGE_LOW @@ -322,14 +316,13 @@ var/temperature = air.temperature var/old_heat_capacity = air.heat_capacity() - var/heat_efficency = min(temperature/(FIRE_MINIMUM_TEMPERATURE_TO_EXIST*100),cached_gases[/datum/gas/oxygen][MOLES],cached_gases[/datum/gas/nitrogen][MOLES]) + var/heat_efficency = min(temperature/(FIRE_MINIMUM_TEMPERATURE_TO_EXIST*100),cached_gases[/datum/gas/oxygen],cached_gases[/datum/gas/nitrogen]) var/energy_used = heat_efficency*NITRYL_FORMATION_ENERGY - ASSERT_GAS(/datum/gas/nitryl,air) - if ((cached_gases[/datum/gas/oxygen][MOLES] - heat_efficency < 0 )|| (cached_gases[/datum/gas/nitrogen][MOLES] - heat_efficency < 0)) //Shouldn't produce gas from nothing. + if ((cached_gases[/datum/gas/oxygen] - heat_efficency < 0 )|| (cached_gases[/datum/gas/nitrogen] - heat_efficency < 0)) //Shouldn't produce gas from nothing. return NO_REACTION - cached_gases[/datum/gas/oxygen][MOLES] -= heat_efficency - cached_gases[/datum/gas/nitrogen][MOLES] -= heat_efficency - cached_gases[/datum/gas/nitryl][MOLES] += heat_efficency*2 + cached_gases[/datum/gas/oxygen] -= heat_efficency + cached_gases[/datum/gas/nitrogen] -= heat_efficency + cached_gases[/datum/gas/nitryl] += heat_efficency*2 if(energy_used > 0) var/new_heat_capacity = air.heat_capacity() @@ -355,14 +348,13 @@ var/pressure = air.return_pressure() var/old_heat_capacity = air.heat_capacity() - var/reaction_efficency = min(1/((pressure/(0.1*ONE_ATMOSPHERE))*(max(cached_gases[/datum/gas/plasma][MOLES]/cached_gases[/datum/gas/nitrous_oxide][MOLES],1))),cached_gases[/datum/gas/nitrous_oxide][MOLES],cached_gases[/datum/gas/plasma][MOLES]/2) + var/reaction_efficency = min(1/((pressure/(0.1*ONE_ATMOSPHERE))*(max(cached_gases[/datum/gas/plasma]/cached_gases[/datum/gas/nitrous_oxide],1))),cached_gases[/datum/gas/nitrous_oxide],cached_gases[/datum/gas/plasma]/2) var/energy_released = 2*reaction_efficency*FIRE_CARBON_ENERGY_RELEASED - if ((cached_gases[/datum/gas/nitrous_oxide][MOLES] - reaction_efficency < 0 )|| (cached_gases[/datum/gas/plasma][MOLES] - (2*reaction_efficency) < 0)) //Shouldn't produce gas from nothing. + if ((cached_gases[/datum/gas/nitrous_oxide] - reaction_efficency < 0 )|| (cached_gases[/datum/gas/plasma] - (2*reaction_efficency) < 0)) //Shouldn't produce gas from nothing. return NO_REACTION - ASSERT_GAS(/datum/gas/bz,air) - cached_gases[/datum/gas/bz][MOLES] += reaction_efficency - cached_gases[/datum/gas/nitrous_oxide][MOLES] -= reaction_efficency - cached_gases[/datum/gas/plasma][MOLES] -= 2*reaction_efficency + cached_gases[/datum/gas/bz] += reaction_efficency + cached_gases[/datum/gas/nitrous_oxide] -= reaction_efficency + cached_gases[/datum/gas/plasma] -= 2*reaction_efficency if(energy_released > 0) @@ -388,16 +380,15 @@ var/list/cached_gases = air.gases var/old_heat_capacity = air.heat_capacity() - var/heat_scale = min(air.temperature/STIMULUM_HEAT_SCALE,cached_gases[/datum/gas/tritium][MOLES],cached_gases[/datum/gas/plasma][MOLES],cached_gases[/datum/gas/nitryl][MOLES]) + var/heat_scale = min(air.temperature/STIMULUM_HEAT_SCALE,cached_gases[/datum/gas/tritium],cached_gases[/datum/gas/plasma],cached_gases[/datum/gas/nitryl]) var/stim_energy_change = heat_scale + STIMULUM_FIRST_RISE*(heat_scale**2) - STIMULUM_FIRST_DROP*(heat_scale**3) + STIMULUM_SECOND_RISE*(heat_scale**4) - STIMULUM_ABSOLUTE_DROP*(heat_scale**5) - ASSERT_GAS(/datum/gas/stimulum,air) - if ((cached_gases[/datum/gas/tritium][MOLES] - heat_scale < 0 )|| (cached_gases[/datum/gas/plasma][MOLES] - heat_scale < 0) || (cached_gases[/datum/gas/nitryl][MOLES] - heat_scale < 0)) //Shouldn't produce gas from nothing. + if ((cached_gases[/datum/gas/tritium] - heat_scale < 0 )|| (cached_gases[/datum/gas/plasma] - heat_scale < 0) || (cached_gases[/datum/gas/nitryl] - heat_scale < 0)) //Shouldn't produce gas from nothing. return NO_REACTION - cached_gases[/datum/gas/stimulum][MOLES]+= heat_scale/10 - cached_gases[/datum/gas/tritium][MOLES] -= heat_scale - cached_gases[/datum/gas/plasma][MOLES] -= heat_scale - cached_gases[/datum/gas/nitryl][MOLES] -= heat_scale + cached_gases[/datum/gas/stimulum]+= heat_scale/10 + cached_gases[/datum/gas/tritium] -= heat_scale + cached_gases[/datum/gas/plasma] -= heat_scale + cached_gases[/datum/gas/nitryl] -= heat_scale if(stim_energy_change) var/new_heat_capacity = air.heat_capacity() @@ -418,15 +409,14 @@ /datum/gas_reaction/nobliumformation/react(datum/gas_mixture/air) var/list/cached_gases = air.gases - air.assert_gases(/datum/gas/hypernoblium,/datum/gas/bz) var/old_heat_capacity = air.heat_capacity() - var/nob_formed = min((cached_gases[/datum/gas/nitrogen][MOLES]+cached_gases[/datum/gas/tritium][MOLES])/100,cached_gases[/datum/gas/tritium][MOLES]/10,cached_gases[/datum/gas/nitrogen][MOLES]/20) - var/energy_taken = nob_formed*(NOBLIUM_FORMATION_ENERGY/(max(cached_gases[/datum/gas/bz][MOLES],1))) - if ((cached_gases[/datum/gas/tritium][MOLES] - 10*nob_formed < 0) || (cached_gases[/datum/gas/nitrogen][MOLES] - 20*nob_formed < 0)) + var/nob_formed = min((cached_gases[/datum/gas/nitrogen]+cached_gases[/datum/gas/tritium])/100,cached_gases[/datum/gas/tritium]/10,cached_gases[/datum/gas/nitrogen]/20) + var/energy_taken = nob_formed*(NOBLIUM_FORMATION_ENERGY/(max(cached_gases[/datum/gas/bz],1))) + if ((cached_gases[/datum/gas/tritium] - 10*nob_formed < 0) || (cached_gases[/datum/gas/nitrogen] - 20*nob_formed < 0)) return NO_REACTION - cached_gases[/datum/gas/tritium][MOLES] -= 10*nob_formed - cached_gases[/datum/gas/nitrogen][MOLES] -= 20*nob_formed - cached_gases[/datum/gas/hypernoblium][MOLES]+= nob_formed + cached_gases[/datum/gas/tritium] -= 10*nob_formed + cached_gases[/datum/gas/nitrogen] -= 20*nob_formed + cached_gases[/datum/gas/hypernoblium]+= nob_formed if (nob_formed) @@ -449,14 +439,13 @@ /datum/gas_reaction/miaster/react(datum/gas_mixture/air, datum/holder) var/list/cached_gases = air.gases // As the name says it, it needs to be dry - if(cached_gases[/datum/gas/water_vapor] && cached_gases[/datum/gas/water_vapor][MOLES]/air.total_moles() > 0.1) + if(cached_gases[/datum/gas/water_vapor] && cached_gases[/datum/gas/water_vapor]/air.total_moles() > 0.1) return //Replace miasma with oxygen - var/cleaned_air = min(cached_gases[/datum/gas/miasma][MOLES], 20 + (air.temperature - FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 70) / 20) - cached_gases[/datum/gas/miasma][MOLES] -= cleaned_air - ASSERT_GAS(/datum/gas/oxygen,air) - cached_gases[/datum/gas/oxygen][MOLES] += cleaned_air + var/cleaned_air = min(cached_gases[/datum/gas/miasma], 20 + (air.temperature - FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 70) / 20) + cached_gases[/datum/gas/miasma] -= cleaned_air + cached_gases[/datum/gas/oxygen] += cleaned_air //Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable air.temperature += cleaned_air * 0.002 diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index 0a7b76cc79..6ea571935b 100644 --- a/code/modules/atmospherics/machinery/airalarm.dm +++ b/code/modules/atmospherics/machinery/airalarm.dm @@ -1,880 +1,880 @@ -/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) - if(min2) src.min2 = min2 - if(min1) src.min1 = min1 - if(max1) src.max1 = max1 - if(max2) 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 - -/datum/tlv/no_checks - min2 = -1 - min1 = -1 - max1 = -1 - max2 = -1 - -/datum/tlv/dangerous - min2 = -1 - min1 = -1 - max1 = 0.2 - max2 = 0.5 - -/obj/item/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 - -#define AALARM_OVERLAY_OFF "alarm_off" -#define AALARM_OVERLAY_GREEN "alarm_green" -#define AALARM_OVERLAY_WARN "alarm_amber" -#define AALARM_OVERLAY_DANGER "alarm_red" - -/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" - use_power = IDLE_POWER_USE - idle_power_usage = 4 - active_power_usage = 8 - power_channel = ENVIRON - req_access = list(ACCESS_ATMOSPHERICS) - max_integrity = 250 - integrity_failure = 80 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) - resistance_flags = FIRE_PROOF - - var/danger_level = 0 - var/mode = AALARM_MODE_SCRUBBING - - var/locked = TRUE - var/aidisabled = 0 - var/shorted = 0 - var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone - var/brightness_on = 1 - - var/frequency = FREQ_ATMOS_CONTROL - var/alarm_frequency = FREQ_ATMOS_ALARMS - var/datum/radio_frequency/radio_connection - - var/list/TLV = list( // Breathable air. - "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa - "temperature" = new/datum/tlv(T0C, T0C+10, T0C+40, T0C+66), - /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa - /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000), - /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10), - /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5), - /datum/gas/plasma = new/datum/tlv/dangerous, - /datum/gas/nitrous_oxide = new/datum/tlv/dangerous, - /datum/gas/bz = new/datum/tlv/dangerous, - /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic - /datum/gas/water_vapor = new/datum/tlv/dangerous, - /datum/gas/tritium = new/datum/tlv/dangerous, - /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects - /datum/gas/nitryl = new/datum/tlv/dangerous, - /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires - ) - -/obj/machinery/airalarm/server // No checks here. - TLV = list( - "pressure" = new/datum/tlv/no_checks, - "temperature" = new/datum/tlv/no_checks, - /datum/gas/oxygen = new/datum/tlv/no_checks, - /datum/gas/nitrogen = new/datum/tlv/no_checks, - /datum/gas/carbon_dioxide = new/datum/tlv/no_checks, - /datum/gas/miasma = new/datum/tlv/no_checks, - /datum/gas/plasma = new/datum/tlv/no_checks, - /datum/gas/nitrous_oxide = new/datum/tlv/no_checks, - /datum/gas/bz = new/datum/tlv/no_checks, - /datum/gas/hypernoblium = new/datum/tlv/no_checks, - /datum/gas/water_vapor = new/datum/tlv/no_checks, - /datum/gas/tritium = new/datum/tlv/no_checks, - /datum/gas/stimulum = new/datum/tlv/no_checks, - /datum/gas/nitryl = new/datum/tlv/no_checks, - /datum/gas/pluoxium = new/datum/tlv/no_checks - ) - -/obj/machinery/airalarm/kitchen_cold_room // Copypasta: to check temperatures. - TLV = list( - "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa - "temperature" = new/datum/tlv(T0C-73.15, T0C-63.15, T0C, T0C+10), - /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa - /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000), - /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10), - /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5), - /datum/gas/plasma = new/datum/tlv/dangerous, - /datum/gas/nitrous_oxide = new/datum/tlv/dangerous, - /datum/gas/bz = new/datum/tlv/dangerous, - /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic - /datum/gas/water_vapor = new/datum/tlv/dangerous, - /datum/gas/tritium = new/datum/tlv/dangerous, - /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects - /datum/gas/nitryl = new/datum/tlv/dangerous, - /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires - ) - -/obj/machinery/airalarm/unlocked - locked = FALSE - -/obj/machinery/airalarm/engine - name = "engine air alarm" - locked = FALSE - req_access = null - req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_ENGINE) - -/obj/machinery/airalarm/mixingchamber - name = "chamber air alarm" - locked = FALSE - req_access = null - req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_TOX, ACCESS_TOX_STORAGE) - -/obj/machinery/airalarm/all_access - name = "all-access air alarm" - desc = "This particular atmos control unit appears to have no access restrictions." - locked = FALSE - req_access = null - req_one_access = null - -/obj/machinery/airalarm/syndicate //general syndicate access - req_access = list(ACCESS_SYNDICATE) - -/obj/machinery/airalarm/directional/north //Pixel offsets get overwritten on New() - dir = SOUTH - pixel_y = 24 - -/obj/machinery/airalarm/directional/south - dir = NORTH - pixel_y = -24 - -/obj/machinery/airalarm/directional/east - dir = WEST - pixel_x = 24 - -/obj/machinery/airalarm/directional/west - dir = EAST - pixel_x = -24 - -//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/Initialize(mapload, ndir, nbuild) - . = ..() - wires = new /datum/wires/airalarm(src) - - if(ndir) - setDir(ndir) - - if(nbuild) - buildstage = 0 - panel_open = TRUE - pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) - pixel_y = (dir & 3)? (dir == 1 ? -24 : 24) : 0 - - if(name == initial(name)) - name = "[get_area_name(src)] Air Alarm" - - power_change() - set_frequency(frequency) - -/obj/machinery/airalarm/Destroy() - SSradio.remove_object(src, frequency) - qdel(wires) - wires = null - return ..() - -/obj/machinery/airalarm/examine(mob/user) - . = ..() - switch(buildstage) - if(0) - to_chat(user, "It is missing air alarm electronics.") - if(1) - to_chat(user, "It is missing wiring.") - if(2) - to_chat(user, "Alt-click to [locked ? "unlock" : "lock"] the interface.") - -/obj/machinery/airalarm/ui_status(mob/user) - if(user.has_unlimited_silicon_privilege && aidisabled) - to_chat(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 = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.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" = (obj_flags & EMAGGED ? 1 : 0), - "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"], - "internal" = info["internal"], - "extdefault"= (info["external"] == ONE_ATMOSPHERE), - "intdefault"= (info["internal"] == 0) - )) - 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_types" = info["filter_types"] - )) - 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" = 1)) - 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(obj_flags & 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 GLOB.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" = GLOB.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", "toggle_filter", "widenet", "scrubbing") - send_signal(device_id, list("[action]" = params["val"]), usr) - . = TRUE - if("excheck") - send_signal(device_id, list("checks" = text2num(params["val"])^1), usr) - . = TRUE - if("incheck") - send_signal(device_id, list("checks" = text2num(params["val"])^2), usr) - . = TRUE - if("set_external_pressure", "set_internal_pressure") - var/area/A = get_area(src) - var/target = input("New target pressure:", name, A.air_vent_info[device_id][(action == "set_external_pressure" ? "external" : "internal")]) as num|null - if(!isnull(target) && !..()) - send_signal(device_id, list("[action]" = target), usr) - . = TRUE - if("reset_external_pressure") - send_signal(device_id, list("reset_external_pressure"), usr) - . = TRUE - if("reset_internal_pressure") - send_signal(device_id, list("reset_internal_pressure"), usr) - . = TRUE - if("threshold") - var/env = params["env"] - if(text2path(env)) - env = text2path(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) - investigate_log(" treshold value for [env]:[name] was set to [value] by [key_name(usr)]",INVESTIGATE_ATMOS) - . = TRUE - if("mode") - mode = text2num(params["mode"]) - investigate_log("was turned to [get_mode_name(mode)] mode by [key_name(usr)]",INVESTIGATE_ATMOS) - apply_mode() - . = TRUE - if("alarm") - var/area/A = get_area(src) - if(A.atmosalert(2, src)) - post_alert(2) - . = TRUE - if("reset") - var/area/A = get_area(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, 1, TRUE)) - return 1 - else - return 0 - -/obj/machinery/airalarm/proc/refresh_all() - var/area/A = get_area(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, mob/user)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise - if(!radio_connection) - return 0 - - var/datum/signal/signal = new(command) - signal.data["tag"] = target - signal.data["sigtype"] = "command" - signal.data["user"] = user - radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM) - - return 1 - -/obj/machinery/airalarm/proc/get_mode_name(mode_value) - switch(mode_value) - if(AALARM_MODE_SCRUBBING) - return "Filtering" - if(AALARM_MODE_CONTAMINATED) - return "Contaminated" - if(AALARM_MODE_VENTING) - return "Draught" - if(AALARM_MODE_REFILL) - return "Refill" - if(AALARM_MODE_PANIC) - return "Panic Siphon" - if(AALARM_MODE_REPLACEMENT) - return "Cycle" - if(AALARM_MODE_SIPHON) - return "Siphon" - if(AALARM_MODE_OFF) - return "Off" - if(AALARM_MODE_FLOOD) - return "Flood" - -/obj/machinery/airalarm/proc/apply_mode() - var/area/A = get_area(src) - switch(mode) - if(AALARM_MODE_SCRUBBING) - for(var/device_id in A.air_scrub_names) - send_signal(device_id, list( - "power" = 1, - "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma), - "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, - "set_filters" = list( - /datum/gas/carbon_dioxide, - /datum/gas/miasma, - /datum/gas/plasma, - /datum/gas/water_vapor, - /datum/gas/hypernoblium, - /datum/gas/nitrous_oxide, - /datum/gas/nitryl, - /datum/gas/tritium, - /datum/gas/bz, - /datum/gas/stimulum, - /datum/gas/pluoxium - ), - "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, - "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma), - "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() - set_light(0) - cut_overlays() - SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) - if(stat & NOPOWER) - icon_state = "alarm0" - return - - if(stat & BROKEN) - icon_state = "alarmx" - return - - if(panel_open) - switch(buildstage) - if(2) - icon_state = "alarmx" - if(1) - icon_state = "alarm_b2" - if(0) - icon_state = "alarm_b1" - return - - icon_state = "alarm1" - var/overlay_state = AALARM_OVERLAY_OFF - var/area/A = get_area(src) - switch(max(danger_level, A.atmosalm)) - if(0) - add_overlay(AALARM_OVERLAY_GREEN) - overlay_state = AALARM_OVERLAY_GREEN - light_color = LIGHT_COLOR_PALEBLUE - set_light(brightness_on) - if(1) - add_overlay(AALARM_OVERLAY_WARN) - overlay_state = AALARM_OVERLAY_WARN - light_color = LIGHT_COLOR_LAVA - set_light(brightness_on) - if(2) - add_overlay(AALARM_OVERLAY_DANGER) - overlay_state = AALARM_OVERLAY_DANGER - light_color = LIGHT_COLOR_RED - set_light(brightness_on) - - SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) - update_light() - -/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() - - return - - -/obj/machinery/airalarm/proc/post_alert(alert_level) - var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency) - - if(!frequency) - return - - var/datum/signal/alert_signal = new(list( - "zone" = get_area_name(src), - "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, range = -1) - -/obj/machinery/airalarm/proc/apply_danger_level() - var/area/A = get_area(src) - - var/new_area_danger_level = 0 - for(var/obj/machinery/airalarm/AA in A) - 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/wirecutters) && panel_open && wires.is_all_cut()) - W.play_tool_sound(src) - to_chat(user, "You cut the final wires.") - new /obj/item/stack/cable_coil(loc, 5) - buildstage = 1 - update_icon() - return - else if(istype(W, /obj/item/screwdriver)) // Opening that Air Alarm up. - W.play_tool_sound(src) - panel_open = !panel_open - to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") - update_icon() - return - else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda))// trying to unlock the interface with an ID card - togglelock(user) - else if(panel_open && is_wire_tool(W)) - wires.interact(user) - return - if(1) - if(istype(W, /obj/item/crowbar)) - user.visible_message("[user.name] removes the electronics from [src.name].",\ - "You start prying out the circuit...") - W.play_tool_sound(src) - if (W.use_tool(src, user, 20)) - if (buildstage == 1) - to_chat(user, "You remove the air alarm electronics.") - new /obj/item/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) - to_chat(user, "You need five lengths of cable to wire the air alarm!") - return - user.visible_message("[user.name] wires the air alarm.", \ - "You start wiring the air alarm...") - if (do_after(user, 20, target = src)) - if (cable.get_amount() >= 5 && buildstage == 1) - cable.use(5) - to_chat(user, "You wire the air alarm.") - wires.repair() - aidisabled = 0 - locked = FALSE - mode = 1 - shorted = 0 - post_alert(0) - buildstage = 2 - update_icon() - return - if(0) - if(istype(W, /obj/item/electronics/airalarm)) - if(user.temporarilyRemoveItemFromInventory(W)) - to_chat(user, "You insert the circuit.") - buildstage = 1 - update_icon() - qdel(W) - return - - if(istype(W, /obj/item/electroadaptive_pseudocircuit)) - var/obj/item/electroadaptive_pseudocircuit/P = W - if(!P.adapt_circuit(user, 25)) - return - user.visible_message("[user] fabricates a circuit and places it into [src].", \ - "You adapt an air alarm circuit and slot it into the assembly.") - buildstage = 1 - update_icon() - return - - if(istype(W, /obj/item/wrench)) - to_chat(user, "You detach \the [src] from the wall.") - W.play_tool_sound(src) - new /obj/item/wallframe/airalarm( user.loc ) - qdel(src) - return - - return ..() - -/obj/machinery/airalarm/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc)) - return - else - togglelock(user) - -/obj/machinery/airalarm/proc/togglelock(mob/living/user) - if(stat & (NOPOWER|BROKEN)) - to_chat(user, "It does nothing!") - else - if(src.allowed(usr) && !wires.is_cut(WIRE_IDSCAN)) - locked = !locked - updateUsrDialog() - to_chat(user, "You [ locked ? "lock" : "unlock"] the air alarm interface.") - else - to_chat(user, "Access denied.") - return - -/obj/machinery/airalarm/power_change() - ..() - if(stat & NOPOWER) - set_light(0) - update_icon() - -/obj/machinery/airalarm/emag_act(mob/user) - if(obj_flags & EMAGGED) - return - obj_flags |= EMAGGED - visible_message("Sparks fly out of [src]!", "You emag [src], disabling its safeties.") - playsound(src, "sparks", 50, 1) - -/obj/machinery/airalarm/obj_break(damage_flag) - ..() - update_icon() - set_light(0) - -/obj/machinery/airalarm/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - new /obj/item/stack/sheet/metal(loc, 2) - var/obj/item/I = new /obj/item/electronics/airalarm(loc) - if(!disassembled) - I.obj_integrity = I.max_integrity * 0.5 - new /obj/item/stack/cable_coil(loc, 3) - qdel(src) - -#undef AALARM_MODE_SCRUBBING -#undef AALARM_MODE_VENTING -#undef AALARM_MODE_PANIC -#undef AALARM_MODE_REPLACEMENT -#undef AALARM_MODE_OFF -#undef AALARM_MODE_FLOOD -#undef AALARM_MODE_SIPHON -#undef AALARM_MODE_CONTAMINATED -#undef AALARM_MODE_REFILL -#undef AALARM_REPORT_TIMEOUT +/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) + if(min2) src.min2 = min2 + if(min1) src.min1 = min1 + if(max1) src.max1 = max1 + if(max2) 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 + +/datum/tlv/no_checks + min2 = -1 + min1 = -1 + max1 = -1 + max2 = -1 + +/datum/tlv/dangerous + min2 = -1 + min1 = -1 + max1 = 0.2 + max2 = 0.5 + +/obj/item/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 + +#define AALARM_OVERLAY_OFF "alarm_off" +#define AALARM_OVERLAY_GREEN "alarm_green" +#define AALARM_OVERLAY_WARN "alarm_amber" +#define AALARM_OVERLAY_DANGER "alarm_red" + +/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" + use_power = IDLE_POWER_USE + idle_power_usage = 4 + active_power_usage = 8 + power_channel = ENVIRON + req_access = list(ACCESS_ATMOSPHERICS) + max_integrity = 250 + integrity_failure = 80 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 90, "acid" = 30) + resistance_flags = FIRE_PROOF + + var/danger_level = 0 + var/mode = AALARM_MODE_SCRUBBING + + var/locked = TRUE + var/aidisabled = 0 + var/shorted = 0 + var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone + var/brightness_on = 1 + + var/frequency = FREQ_ATMOS_CONTROL + var/alarm_frequency = FREQ_ATMOS_ALARMS + var/datum/radio_frequency/radio_connection + + var/list/TLV = list( // Breathable air. + "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa + "temperature" = new/datum/tlv(T0C, T0C+10, T0C+40, T0C+66), + /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa + /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000), + /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10), + /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5), + /datum/gas/plasma = new/datum/tlv/dangerous, + /datum/gas/nitrous_oxide = new/datum/tlv/dangerous, + /datum/gas/bz = new/datum/tlv/dangerous, + /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic + /datum/gas/water_vapor = new/datum/tlv/dangerous, + /datum/gas/tritium = new/datum/tlv/dangerous, + /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects + /datum/gas/nitryl = new/datum/tlv/dangerous, + /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires + ) + +/obj/machinery/airalarm/server // No checks here. + TLV = list( + "pressure" = new/datum/tlv/no_checks, + "temperature" = new/datum/tlv/no_checks, + /datum/gas/oxygen = new/datum/tlv/no_checks, + /datum/gas/nitrogen = new/datum/tlv/no_checks, + /datum/gas/carbon_dioxide = new/datum/tlv/no_checks, + /datum/gas/miasma = new/datum/tlv/no_checks, + /datum/gas/plasma = new/datum/tlv/no_checks, + /datum/gas/nitrous_oxide = new/datum/tlv/no_checks, + /datum/gas/bz = new/datum/tlv/no_checks, + /datum/gas/hypernoblium = new/datum/tlv/no_checks, + /datum/gas/water_vapor = new/datum/tlv/no_checks, + /datum/gas/tritium = new/datum/tlv/no_checks, + /datum/gas/stimulum = new/datum/tlv/no_checks, + /datum/gas/nitryl = new/datum/tlv/no_checks, + /datum/gas/pluoxium = new/datum/tlv/no_checks + ) + +/obj/machinery/airalarm/kitchen_cold_room // Copypasta: to check temperatures. + TLV = list( + "pressure" = new/datum/tlv(ONE_ATMOSPHERE * 0.8, ONE_ATMOSPHERE* 0.9, ONE_ATMOSPHERE * 1.1, ONE_ATMOSPHERE * 1.2), // kPa + "temperature" = new/datum/tlv(T0C-73.15, T0C-63.15, T0C, T0C+10), + /datum/gas/oxygen = new/datum/tlv(16, 19, 135, 140), // Partial pressure, kpa + /datum/gas/nitrogen = new/datum/tlv(-1, -1, 1000, 1000), + /datum/gas/carbon_dioxide = new/datum/tlv(-1, -1, 5, 10), + /datum/gas/miasma = new/datum/tlv/(-1, -1, 2, 5), + /datum/gas/plasma = new/datum/tlv/dangerous, + /datum/gas/nitrous_oxide = new/datum/tlv/dangerous, + /datum/gas/bz = new/datum/tlv/dangerous, + /datum/gas/hypernoblium = new/datum/tlv(-1, -1, 1000, 1000), // Hyper-Noblium is inert and nontoxic + /datum/gas/water_vapor = new/datum/tlv/dangerous, + /datum/gas/tritium = new/datum/tlv/dangerous, + /datum/gas/stimulum = new/datum/tlv(-1, -1, 1000, 1000), // Stimulum has only positive effects + /datum/gas/nitryl = new/datum/tlv/dangerous, + /datum/gas/pluoxium = new/datum/tlv(-1, -1, 1000, 1000) // Unlike oxygen, pluoxium does not fuel plasma/tritium fires + ) + +/obj/machinery/airalarm/unlocked + locked = FALSE + +/obj/machinery/airalarm/engine + name = "engine air alarm" + locked = FALSE + req_access = null + req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_ENGINE) + +/obj/machinery/airalarm/mixingchamber + name = "chamber air alarm" + locked = FALSE + req_access = null + req_one_access = list(ACCESS_ATMOSPHERICS, ACCESS_TOX, ACCESS_TOX_STORAGE) + +/obj/machinery/airalarm/all_access + name = "all-access air alarm" + desc = "This particular atmos control unit appears to have no access restrictions." + locked = FALSE + req_access = null + req_one_access = null + +/obj/machinery/airalarm/syndicate //general syndicate access + req_access = list(ACCESS_SYNDICATE) + +/obj/machinery/airalarm/directional/north //Pixel offsets get overwritten on New() + dir = SOUTH + pixel_y = 24 + +/obj/machinery/airalarm/directional/south + dir = NORTH + pixel_y = -24 + +/obj/machinery/airalarm/directional/east + dir = WEST + pixel_x = 24 + +/obj/machinery/airalarm/directional/west + dir = EAST + pixel_x = -24 + +//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/Initialize(mapload, ndir, nbuild) + . = ..() + wires = new /datum/wires/airalarm(src) + + if(ndir) + setDir(ndir) + + if(nbuild) + buildstage = 0 + panel_open = TRUE + pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) + pixel_y = (dir & 3)? (dir == 1 ? -24 : 24) : 0 + + if(name == initial(name)) + name = "[get_area_name(src)] Air Alarm" + + power_change() + set_frequency(frequency) + +/obj/machinery/airalarm/Destroy() + SSradio.remove_object(src, frequency) + qdel(wires) + wires = null + return ..() + +/obj/machinery/airalarm/examine(mob/user) + . = ..() + switch(buildstage) + if(0) + to_chat(user, "It is missing air alarm electronics.") + if(1) + to_chat(user, "It is missing wiring.") + if(2) + to_chat(user, "Alt-click to [locked ? "unlock" : "lock"] the interface.") + +/obj/machinery/airalarm/ui_status(mob/user) + if(user.has_unlimited_silicon_privilege && aidisabled) + to_chat(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 = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.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" = (obj_flags & EMAGGED ? 1 : 0), + "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" = GLOB.meta_gas_names[gas_id], + "value" = environment.gases[gas_id] / total_moles * 100, + "unit" = "%", + "danger_level" = cur_tlv.get_danger_level(environment.gases[gas_id] * 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"], + "internal" = info["internal"], + "extdefault"= (info["external"] == ONE_ATMOSPHERE), + "intdefault"= (info["internal"] == 0) + )) + 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_types" = info["filter_types"] + )) + 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" = 1)) + 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(obj_flags & 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 GLOB.meta_gas_names) + if(!(gas_id in TLV)) // We're not interested in this gas, it seems. + continue + selected = TLV[gas_id] + thresholds += list(list("name" = GLOB.meta_gas_names[gas_id], "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", "toggle_filter", "widenet", "scrubbing") + send_signal(device_id, list("[action]" = params["val"]), usr) + . = TRUE + if("excheck") + send_signal(device_id, list("checks" = text2num(params["val"])^1), usr) + . = TRUE + if("incheck") + send_signal(device_id, list("checks" = text2num(params["val"])^2), usr) + . = TRUE + if("set_external_pressure", "set_internal_pressure") + var/area/A = get_area(src) + var/target = input("New target pressure:", name, A.air_vent_info[device_id][(action == "set_external_pressure" ? "external" : "internal")]) as num|null + if(!isnull(target) && !..()) + send_signal(device_id, list("[action]" = target), usr) + . = TRUE + if("reset_external_pressure") + send_signal(device_id, list("reset_external_pressure"), usr) + . = TRUE + if("reset_internal_pressure") + send_signal(device_id, list("reset_internal_pressure"), usr) + . = TRUE + if("threshold") + var/env = params["env"] + if(text2path(env)) + env = text2path(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) + investigate_log(" treshold value for [env]:[name] was set to [value] by [key_name(usr)]",INVESTIGATE_ATMOS) + . = TRUE + if("mode") + mode = text2num(params["mode"]) + investigate_log("was turned to [get_mode_name(mode)] mode by [key_name(usr)]",INVESTIGATE_ATMOS) + apply_mode() + . = TRUE + if("alarm") + var/area/A = get_area(src) + if(A.atmosalert(2, src)) + post_alert(2) + . = TRUE + if("reset") + var/area/A = get_area(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, 1, TRUE)) + return 1 + else + return 0 + +/obj/machinery/airalarm/proc/refresh_all() + var/area/A = get_area(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, mob/user)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise + if(!radio_connection) + return 0 + + var/datum/signal/signal = new(command) + signal.data["tag"] = target + signal.data["sigtype"] = "command" + signal.data["user"] = user + radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM) + + return 1 + +/obj/machinery/airalarm/proc/get_mode_name(mode_value) + switch(mode_value) + if(AALARM_MODE_SCRUBBING) + return "Filtering" + if(AALARM_MODE_CONTAMINATED) + return "Contaminated" + if(AALARM_MODE_VENTING) + return "Draught" + if(AALARM_MODE_REFILL) + return "Refill" + if(AALARM_MODE_PANIC) + return "Panic Siphon" + if(AALARM_MODE_REPLACEMENT) + return "Cycle" + if(AALARM_MODE_SIPHON) + return "Siphon" + if(AALARM_MODE_OFF) + return "Off" + if(AALARM_MODE_FLOOD) + return "Flood" + +/obj/machinery/airalarm/proc/apply_mode() + var/area/A = get_area(src) + switch(mode) + if(AALARM_MODE_SCRUBBING) + for(var/device_id in A.air_scrub_names) + send_signal(device_id, list( + "power" = 1, + "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma), + "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, + "set_filters" = list( + /datum/gas/carbon_dioxide, + /datum/gas/miasma, + /datum/gas/plasma, + /datum/gas/water_vapor, + /datum/gas/hypernoblium, + /datum/gas/nitrous_oxide, + /datum/gas/nitryl, + /datum/gas/tritium, + /datum/gas/bz, + /datum/gas/stimulum, + /datum/gas/pluoxium + ), + "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, + "set_filters" = list(/datum/gas/carbon_dioxide, /datum/gas/miasma), + "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() + set_light(0) + cut_overlays() + SSvis_overlays.remove_vis_overlay(src, managed_vis_overlays) + if(stat & NOPOWER) + icon_state = "alarm0" + return + + if(stat & BROKEN) + icon_state = "alarmx" + return + + if(panel_open) + switch(buildstage) + if(2) + icon_state = "alarmx" + if(1) + icon_state = "alarm_b2" + if(0) + icon_state = "alarm_b1" + return + + icon_state = "alarm1" + var/overlay_state = AALARM_OVERLAY_OFF + var/area/A = get_area(src) + switch(max(danger_level, A.atmosalm)) + if(0) + add_overlay(AALARM_OVERLAY_GREEN) + overlay_state = AALARM_OVERLAY_GREEN + light_color = LIGHT_COLOR_PALEBLUE + set_light(brightness_on) + if(1) + add_overlay(AALARM_OVERLAY_WARN) + overlay_state = AALARM_OVERLAY_WARN + light_color = LIGHT_COLOR_LAVA + set_light(brightness_on) + if(2) + add_overlay(AALARM_OVERLAY_DANGER) + overlay_state = AALARM_OVERLAY_DANGER + light_color = LIGHT_COLOR_RED + set_light(brightness_on) + + SSvis_overlays.add_vis_overlay(src, icon, overlay_state, ABOVE_LIGHTING_LAYER, ABOVE_LIGHTING_PLANE, dir) + update_light() + +/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] * partial_pressure)) + + GAS_GARBAGE_COLLECT(environment.gases) + + 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() + + return + + +/obj/machinery/airalarm/proc/post_alert(alert_level) + var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency) + + if(!frequency) + return + + var/datum/signal/alert_signal = new(list( + "zone" = get_area_name(src), + "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, range = -1) + +/obj/machinery/airalarm/proc/apply_danger_level() + var/area/A = get_area(src) + + var/new_area_danger_level = 0 + for(var/obj/machinery/airalarm/AA in A) + 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/wirecutters) && panel_open && wires.is_all_cut()) + W.play_tool_sound(src) + to_chat(user, "You cut the final wires.") + new /obj/item/stack/cable_coil(loc, 5) + buildstage = 1 + update_icon() + return + else if(istype(W, /obj/item/screwdriver)) // Opening that Air Alarm up. + W.play_tool_sound(src) + panel_open = !panel_open + to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"].") + update_icon() + return + else if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda))// trying to unlock the interface with an ID card + togglelock(user) + else if(panel_open && is_wire_tool(W)) + wires.interact(user) + return + if(1) + if(istype(W, /obj/item/crowbar)) + user.visible_message("[user.name] removes the electronics from [src.name].",\ + "You start prying out the circuit...") + W.play_tool_sound(src) + if (W.use_tool(src, user, 20)) + if (buildstage == 1) + to_chat(user, "You remove the air alarm electronics.") + new /obj/item/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) + to_chat(user, "You need five lengths of cable to wire the air alarm!") + return + user.visible_message("[user.name] wires the air alarm.", \ + "You start wiring the air alarm...") + if (do_after(user, 20, target = src)) + if (cable.get_amount() >= 5 && buildstage == 1) + cable.use(5) + to_chat(user, "You wire the air alarm.") + wires.repair() + aidisabled = 0 + locked = FALSE + mode = 1 + shorted = 0 + post_alert(0) + buildstage = 2 + update_icon() + return + if(0) + if(istype(W, /obj/item/electronics/airalarm)) + if(user.temporarilyRemoveItemFromInventory(W)) + to_chat(user, "You insert the circuit.") + buildstage = 1 + update_icon() + qdel(W) + return + + if(istype(W, /obj/item/electroadaptive_pseudocircuit)) + var/obj/item/electroadaptive_pseudocircuit/P = W + if(!P.adapt_circuit(user, 25)) + return + user.visible_message("[user] fabricates a circuit and places it into [src].", \ + "You adapt an air alarm circuit and slot it into the assembly.") + buildstage = 1 + update_icon() + return + + if(istype(W, /obj/item/wrench)) + to_chat(user, "You detach \the [src] from the wall.") + W.play_tool_sound(src) + new /obj/item/wallframe/airalarm( user.loc ) + qdel(src) + return + + return ..() + +/obj/machinery/airalarm/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc)) + return + else + togglelock(user) + +/obj/machinery/airalarm/proc/togglelock(mob/living/user) + if(stat & (NOPOWER|BROKEN)) + to_chat(user, "It does nothing!") + else + if(src.allowed(usr) && !wires.is_cut(WIRE_IDSCAN)) + locked = !locked + updateUsrDialog() + to_chat(user, "You [ locked ? "lock" : "unlock"] the air alarm interface.") + else + to_chat(user, "Access denied.") + return + +/obj/machinery/airalarm/power_change() + ..() + if(stat & NOPOWER) + set_light(0) + update_icon() + +/obj/machinery/airalarm/emag_act(mob/user) + if(obj_flags & EMAGGED) + return + obj_flags |= EMAGGED + visible_message("Sparks fly out of [src]!", "You emag [src], disabling its safeties.") + playsound(src, "sparks", 50, 1) + +/obj/machinery/airalarm/obj_break(damage_flag) + ..() + update_icon() + set_light(0) + +/obj/machinery/airalarm/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + new /obj/item/stack/sheet/metal(loc, 2) + var/obj/item/I = new /obj/item/electronics/airalarm(loc) + if(!disassembled) + I.obj_integrity = I.max_integrity * 0.5 + new /obj/item/stack/cable_coil(loc, 3) + qdel(src) + +#undef AALARM_MODE_SCRUBBING +#undef AALARM_MODE_VENTING +#undef AALARM_MODE_PANIC +#undef AALARM_MODE_REPLACEMENT +#undef AALARM_MODE_OFF +#undef AALARM_MODE_FLOOD +#undef AALARM_MODE_SIPHON +#undef AALARM_MODE_CONTAMINATED +#undef AALARM_MODE_REFILL +#undef AALARM_REPORT_TIMEOUT diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index 1e52e238cf..52e8d9eb83 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -30,7 +30,7 @@ to_chat(user,"You maximize the pressure on the [src].") investigate_log("Pump, [src.name], was maximized by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS) message_admins("Pump, [src.name], was maximized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]") - + /obj/machinery/atmospherics/components/trinary/filter/layer1 piping_layer = PIPING_LAYER_MIN pixel_x = -PIPING_LAYER_P_X @@ -178,11 +178,10 @@ var/datum/gas_mixture/filtered_out = new filtered_out.temperature = removed.temperature - filtered_out.add_gas(filter_type) - filtered_out.gases[filter_type][MOLES] = removed.gases[filter_type][MOLES] + filtered_out.gases[filter_type] = removed.gases[filter_type] - removed.gases[filter_type][MOLES] = 0 - removed.garbage_collect() + removed.gases[filter_type] = 0 + GAS_GARBAGE_COLLECT(removed.gases) var/datum/gas_mixture/target = (air2.return_pressure() < target_pressure ? air2 : air1) //if there's no room for the filtered gas; just leave it in air1 target.merge(filtered_out) @@ -210,9 +209,8 @@ data["filter_types"] = list() data["filter_types"] += list(list("name" = "Nothing", "path" = "", "selected" = !filter_type)) - for(var/path in GLOB.meta_gas_info) - var/list/gas = GLOB.meta_gas_info[path] - data["filter_types"] += list(list("name" = gas[META_GAS_NAME], "id" = gas[META_GAS_ID], "selected" = (path == gas_id2path(filter_type)))) + for(var/path in GLOB.meta_gas_ids) + data["filter_types"] += list(list("name" = GLOB.meta_gas_names[path], "id" = GLOB.meta_gas_ids[path], "selected" = (path == gas_id2path(filter_type)))) return data @@ -243,9 +241,9 @@ filter_type = null var/filter_name = "nothing" var/gas = gas_id2path(params["mode"]) - if(gas in GLOB.meta_gas_info) + if(gas in GLOB.meta_gas_names) filter_type = gas - filter_name = GLOB.meta_gas_info[gas][META_GAS_NAME] + filter_name = GLOB.meta_gas_names[gas] investigate_log("was set to filter [filter_name] by [key_name(usr)]", INVESTIGATE_ATMOS) . = TRUE update_icon() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 9b69b2fa6f..bfe60cd573 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -183,8 +183,8 @@ if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic. beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. beaker.reagents.reaction(occupant, VAPOR) - air1.gases[/datum/gas/oxygen][MOLES] -= max(0,air1.gases[/datum/gas/oxygen][MOLES] - 2 / efficiency) //Let's use gas for this - air1.garbage_collect() + air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this + GAS_GARBAGE_COLLECT(air1.gases) if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). reagent_transfer = 0 @@ -198,7 +198,7 @@ var/datum/gas_mixture/air1 = airs[1] - if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen][MOLES] < 5) // Turn off if the machine won't work. + if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen] < 5) // Turn off if the machine won't work. on = FALSE update_icon() return @@ -220,8 +220,8 @@ air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB) mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) - air1.gases[/datum/gas/oxygen][MOLES] = max(0,air1.gases[/datum/gas/oxygen][MOLES] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. - air1.garbage_collect() + air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. + GAS_GARBAGE_COLLECT(air1.gases) /obj/machinery/atmospherics/components/unary/cryo_cell/power_change() ..() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm b/code/modules/atmospherics/machinery/components/unary_devices/tank.dm index 1a9d1cff30..4f16406456 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/tank.dm @@ -17,9 +17,8 @@ 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]])" + air_contents.gases[gas_type] = AIR_CONTENTS + name = "[name] ([GLOB.meta_gas_names[gas_type]])" /obj/machinery/atmospherics/components/unary/tank/carbon_dioxide gas_type = /datum/gas/carbon_dioxide @@ -44,6 +43,5 @@ /obj/machinery/atmospherics/components/unary/tank/air/New() ..() var/datum/gas_mixture/air_contents = airs[1] - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - air_contents.gases[/datum/gas/oxygen][MOLES] = AIR_CONTENTS * 0.2 - air_contents.gases[/datum/gas/nitrogen][MOLES] = AIR_CONTENTS * 0.8 + air_contents.gases[/datum/gas/oxygen] = AIR_CONTENTS * 0.2 + air_contents.gases[/datum/gas/nitrogen] = AIR_CONTENTS * 0.8 diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm index ee819d4f74..7f40630a86 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -1,330 +1,328 @@ -#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 = IDLE_POWER_USE - idle_power_usage = 10 - active_power_usage = 60 - can_unwrench = TRUE - welded = FALSE - level = 1 - layer = GAS_SCRUBBER_LAYER - - var/id_tag = null - var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing - - var/filter_types = list(/datum/gas/carbon_dioxide) - 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 = FREQ_ATMOS_CONTROL - var/datum/radio_frequency/radio_connection - var/radio_filter_out - var/radio_filter_in - - pipe_state = "scrubber" - -/obj/machinery/atmospherics/components/unary/vent_scrubber/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/New() - ..() - if(!id_tag) - id_tag = assign_uid_vents() - - for(var/f in filter_types) - if(istext(f)) - filter_types -= f - filter_types += gas_id2path(f) - -/obj/machinery/atmospherics/components/unary/vent_scrubber/on - on = TRUE - icon_state = "scrub_map_on" - -/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy() - var/area/A = get_area(src) - if (A) - A.air_scrub_names -= id_tag - A.air_scrub_info -= id_tag - - SSradio.remove_object(src,frequency) - radio_connection = null - adjacent_turfs.Cut() - return ..() - -/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power() - if(!on || welded || !is_operational() || !powered(power_channel)) - return FALSE - - var/amount = idle_power_usage - - if(scrubbing & SCRUBBING) - amount += idle_power_usage * length(filter_types) - else //scrubbing == SIPHONING - amount = active_power_usage - - if(widenet) - amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2)) - use_power(amount, power_channel) - return TRUE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes() - cut_overlays() - if(showpipe) - add_overlay(getpipeimage(icon, "scrub_cap", initialize_directions)) - - if(welded) - icon_state = "scrub_welded" - return - - if(!nodes[1] || !on || !is_operational()) - icon_state = "scrub_off" - return - - if(scrubbing & SCRUBBING) - if(widenet) - icon_state = "scrub_wide" - else - 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 FALSE - - var/list/f_types = list() - for(var/path in GLOB.meta_gas_info) - var/list/gas = GLOB.meta_gas_info[path] - f_types += list(list("gas_id" = gas[META_GAS_ID], "gas_name" = gas[META_GAS_NAME], "enabled" = (path in filter_types))) - - var/datum/signal/signal = new(list( - "tag" = id_tag, - "frequency" = frequency, - "device" = "VS", - "timestamp" = world.time, - "power" = on, - "scrubbing" = scrubbing, - "widenet" = widenet, - "filter_types" = f_types, - "sigtype" = "status" - )) - - var/area/A = get_area(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 TRUE - -/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(welded || !is_operational()) - return FALSE - if(!nodes[1] || !on) - on = FALSE - return FALSE - scrub(loc) - if(widenet) - for(var/turf/tile in adjacent_turfs) - scrub(tile) - return TRUE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile) - if(!istype(tile)) - return FALSE - var/datum/gas_mixture/environment = tile.return_air() - var/datum/gas_mixture/air_contents = airs[1] - var/list/env_gases = environment.gases - - if(air_contents.return_pressure() >= 50*ONE_ATMOSPHERE) - return FALSE - - if(scrubbing & SCRUBBING) - if(length(env_gases & filter_types)) - 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) - - //Nothing left to remove from the tile - if(isnull(removed)) - return FALSE - - var/list/removed_gases = removed.gases - - //Filter it - var/datum/gas_mixture/filtered_out = new - var/list/filtered_gases = filtered_out.gases - filtered_out.temperature = removed.temperature - - for(var/gas in filter_types & removed_gases) - filtered_out.add_gas(gas) - filtered_gases[gas][MOLES] = removed_gases[gas][MOLES] - removed_gases[gas][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 - - 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 TRUE - -//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(!is_operational() || !signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command")) - return 0 - - var/mob/signal_sender = signal.data["user"] - - 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 - - var/old_scrubbing = scrubbing - if("scrubbing" in signal.data) - scrubbing = text2num(signal.data["scrubbing"]) - if("toggle_scrubbing" in signal.data) - scrubbing = !scrubbing - if(scrubbing != old_scrubbing) - investigate_log(" was toggled to [scrubbing ? "scrubbing" : "siphon"] mode by [key_name(signal_sender)]",INVESTIGATE_ATMOS) - - if("toggle_filter" in signal.data) - filter_types ^= gas_id2path(signal.data["toggle_filter"]) - - if("set_filters" in signal.data) - filter_types = list() - for(var/gas in signal.data["set_filters"]) - filter_types += gas_id2path(gas) - - 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/welder_act(mob/living/user, obj/item/I) - if(!I.tool_start_check(user, amount=0)) - return TRUE - to_chat(user, "Now welding the scrubber.") - if(I.use_tool(src, user, 20, volume=50)) - if(!welded) - user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.") - welded = TRUE - else - user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.") - welded = FALSE - update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) - pipe_vision_img.plane = ABOVE_HUD_PLANE - return TRUE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/can_unwrench(mob/user) - . = ..() - if(. && on && is_operational()) - to_chat(user, "You cannot unwrench [src], turn it off first!") - return FALSE - -/obj/machinery/atmospherics/components/unary/vent_scrubber/examine(mob/user) - ..() - if(welded) - to_chat(user, "It seems welded shut.") - -/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 = FALSE - update_icon() - pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) - pipe_vision_img.plane = ABOVE_HUD_PLANE - playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) - - - -#undef SIPHONING -#undef SCRUBBING +#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 = IDLE_POWER_USE + idle_power_usage = 10 + active_power_usage = 60 + can_unwrench = TRUE + welded = FALSE + level = 1 + layer = GAS_SCRUBBER_LAYER + + var/id_tag = null + var/scrubbing = SCRUBBING //0 = siphoning, 1 = scrubbing + + var/filter_types = list(/datum/gas/carbon_dioxide) + 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 = FREQ_ATMOS_CONTROL + var/datum/radio_frequency/radio_connection + var/radio_filter_out + var/radio_filter_in + + pipe_state = "scrubber" + +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/New() + ..() + if(!id_tag) + id_tag = assign_uid_vents() + + for(var/f in filter_types) + if(istext(f)) + filter_types -= f + filter_types += gas_id2path(f) + +/obj/machinery/atmospherics/components/unary/vent_scrubber/on + on = TRUE + icon_state = "scrub_map_on" + +/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/on/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/unary/vent_scrubber/Destroy() + var/area/A = get_area(src) + if (A) + A.air_scrub_names -= id_tag + A.air_scrub_info -= id_tag + + SSradio.remove_object(src,frequency) + radio_connection = null + adjacent_turfs.Cut() + return ..() + +/obj/machinery/atmospherics/components/unary/vent_scrubber/auto_use_power() + if(!on || welded || !is_operational() || !powered(power_channel)) + return FALSE + + var/amount = idle_power_usage + + if(scrubbing & SCRUBBING) + amount += idle_power_usage * length(filter_types) + else //scrubbing == SIPHONING + amount = active_power_usage + + if(widenet) + amount += amount * (adjacent_turfs.len * (adjacent_turfs.len / 2)) + use_power(amount, power_channel) + return TRUE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/update_icon_nopipes() + cut_overlays() + if(showpipe) + add_overlay(getpipeimage(icon, "scrub_cap", initialize_directions)) + + if(welded) + icon_state = "scrub_welded" + return + + if(!nodes[1] || !on || !is_operational()) + icon_state = "scrub_off" + return + + if(scrubbing & SCRUBBING) + if(widenet) + icon_state = "scrub_wide" + else + 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 FALSE + + var/list/f_types = list() + for(var/path in GLOB.meta_gas_ids) + f_types += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in filter_types))) + + var/datum/signal/signal = new(list( + "tag" = id_tag, + "frequency" = frequency, + "device" = "VS", + "timestamp" = world.time, + "power" = on, + "scrubbing" = scrubbing, + "widenet" = widenet, + "filter_types" = f_types, + "sigtype" = "status" + )) + + var/area/A = get_area(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 TRUE + +/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(welded || !is_operational()) + return FALSE + if(!nodes[1] || !on) + on = FALSE + return FALSE + scrub(loc) + if(widenet) + for(var/turf/tile in adjacent_turfs) + scrub(tile) + return TRUE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/proc/scrub(var/turf/tile) + if(!istype(tile)) + return FALSE + var/datum/gas_mixture/environment = tile.return_air() + var/datum/gas_mixture/air_contents = airs[1] + var/list/env_gases = environment.gases + + if(air_contents.return_pressure() >= 50*ONE_ATMOSPHERE) + return FALSE + + if(scrubbing & SCRUBBING) + if(length(env_gases & filter_types)) + 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) + + //Nothing left to remove from the tile + if(isnull(removed)) + return FALSE + + var/list/removed_gases = removed.gases + + //Filter it + var/datum/gas_mixture/filtered_out = new + var/list/filtered_gases = filtered_out.gases + filtered_out.temperature = removed.temperature + + for(var/gas in filter_types & removed_gases) + filtered_gases[gas] = removed_gases[gas] + removed_gases[gas] = 0 + + GAS_GARBAGE_COLLECT(removed.gases) + + //Remix the resulting gases + air_contents.merge(filtered_out) + tile.assume_air(removed) + tile.air_update_turf() + + else //Just siphoning all air + + 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 TRUE + +//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(!is_operational() || !signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command")) + return 0 + + var/mob/signal_sender = signal.data["user"] + + 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 + + var/old_scrubbing = scrubbing + if("scrubbing" in signal.data) + scrubbing = text2num(signal.data["scrubbing"]) + if("toggle_scrubbing" in signal.data) + scrubbing = !scrubbing + if(scrubbing != old_scrubbing) + investigate_log(" was toggled to [scrubbing ? "scrubbing" : "siphon"] mode by [key_name(signal_sender)]",INVESTIGATE_ATMOS) + + if("toggle_filter" in signal.data) + filter_types ^= gas_id2path(signal.data["toggle_filter"]) + + if("set_filters" in signal.data) + filter_types = list() + for(var/gas in signal.data["set_filters"]) + filter_types += gas_id2path(gas) + + 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/welder_act(mob/living/user, obj/item/I) + if(!I.tool_start_check(user, amount=0)) + return TRUE + to_chat(user, "Now welding the scrubber.") + if(I.use_tool(src, user, 20, volume=50)) + if(!welded) + user.visible_message("[user] welds the scrubber shut.","You weld the scrubber shut.", "You hear welding.") + welded = TRUE + else + user.visible_message("[user] unwelds the scrubber.", "You unweld the scrubber.", "You hear welding.") + welded = FALSE + update_icon() + pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img.plane = ABOVE_HUD_PLANE + return TRUE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/can_unwrench(mob/user) + . = ..() + if(. && on && is_operational()) + to_chat(user, "You cannot unwrench [src], turn it off first!") + return FALSE + +/obj/machinery/atmospherics/components/unary/vent_scrubber/examine(mob/user) + ..() + if(welded) + to_chat(user, "It seems welded shut.") + +/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 = FALSE + update_icon() + pipe_vision_img = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) + pipe_vision_img.plane = ABOVE_HUD_PLANE + playsound(loc, 'sound/weapons/bladeslice.ogg', 100, 1) + + + +#undef SIPHONING +#undef SCRUBBING diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index c4fa387ab0..38178a4339 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -145,7 +145,7 @@ var/member_gases = member.air_temporary.gases for(var/id in member_gases) - member_gases[id][MOLES] *= member.volume/air.volume + member_gases[id] *= member.volume/air.volume member.air_temporary.temperature = air.temperature @@ -254,4 +254,4 @@ 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 + G_gases[id] *= G.volume/total_gas_mixture.volume diff --git a/code/modules/atmospherics/machinery/other/miner.dm b/code/modules/atmospherics/machinery/other/miner.dm index 20251418bc..adb17b4e94 100644 --- a/code/modules/atmospherics/machinery/other/miner.dm +++ b/code/modules/atmospherics/machinery/other/miner.dm @@ -131,8 +131,7 @@ if(!isopenturf(O)) return FALSE var/datum/gas_mixture/merger = new - merger.assert_gas(spawn_id) - merger.gases[spawn_id][MOLES] = (spawn_mol) + merger.gases[spawn_id] = (spawn_mol) merger.temperature = spawn_temp O.assume_air(merger) O.air_update_turf(TRUE) diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index d23a5df958..bad2b85bfe 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -204,16 +204,14 @@ /obj/machinery/portable_atmospherics/canister/proc/create_gas() if(gas_type) - air_contents.add_gas(gas_type) if(starter_temp) air_contents.temperature = starter_temp - air_contents.gases[gas_type][MOLES] = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) + air_contents.gases[gas_type] = (maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) if(starter_temp) air_contents.temperature = starter_temp /obj/machinery/portable_atmospherics/canister/air/create_gas() - air_contents.add_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) - air_contents.gases[/datum/gas/nitrogen][MOLES] = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) + air_contents.gases[/datum/gas/oxygen] = (O2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) + air_contents.gases[/datum/gas/nitrogen] = (N2STANDARD * maximum_pressure * filled) * air_contents.volume / (R_IDEAL_GAS_EQUATION * air_contents.temperature) #define HOLDING (1<<0) #define CONNECTED (1<<1) @@ -439,10 +437,10 @@ var/list/danger = list() for(var/id in air_contents.gases) var/gas = air_contents.gases[id] - if(!gas[GAS_META][META_GAS_DANGER]) + if(!GLOB.meta_gas_dangers[id]) continue - if(gas[MOLES] > (gas[GAS_META][META_GAS_MOLES_VISIBLE] || MOLES_GAS_VISIBLE)) //if moles_visible is undefined, default to default visibility - danger[gas[GAS_META][META_GAS_NAME]] = gas[MOLES] //ex. "plasma" = 20 + if(gas > (GLOB.meta_gas_visibility[id] || MOLES_GAS_VISIBLE)) //if moles_visible is undefined, default to default visibility + danger[GLOB.meta_gas_names[id]] = gas //ex. "plasma" = 20 if(danger.len) message_admins("[ADMIN_LOOKUPFLW(usr)] opened a canister that contains the following at [ADMIN_VERBOSEJMP(src)]:") diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm index 29a454f59a..28cdb56e3d 100644 --- a/code/modules/atmospherics/machinery/portable/scrubber.dm +++ b/code/modules/atmospherics/machinery/portable/scrubber.dm @@ -1,146 +1,144 @@ -/obj/machinery/portable_atmospherics/scrubber - name = "portable air scrubber" - icon_state = "pscrubber:0" - density = TRUE - - var/on = FALSE - var/volume_rate = 1000 - volume = 1000 - - var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor) - -/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(. & EMP_PROTECT_SELF) - return - 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 = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.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, 435, 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) - - data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers - data["filter_types"] = list() - for(var/path in GLOB.meta_gas_info) - var/list/gas = GLOB.meta_gas_info[path] - data["filter_types"] += list(list("gas_id" = gas[META_GAS_ID], "gas_name" = gas[META_GAS_NAME], "enabled" = (path in scrubbing))) - - 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.forceMove(drop_location()) - holding = null - . = TRUE - if("toggle_filter") - scrubbing ^= gas_id2path(params["val"]) - . = 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 = on ? ACTIVE_POWER_USE : IDLE_POWER_USE - 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/W, mob/user) - if(default_unfasten_wrench(user, W)) - if(!movable) - on = FALSE - else - return ..() +/obj/machinery/portable_atmospherics/scrubber + name = "portable air scrubber" + icon_state = "pscrubber:0" + density = TRUE + + var/on = FALSE + var/volume_rate = 1000 + volume = 1000 + + var/list/scrubbing = list(/datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitryl, /datum/gas/tritium, /datum/gas/hypernoblium, /datum/gas/water_vapor) + +/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.gases[gas] = filtering.gases[gas] // Shuffle the "bad" gasses to the filtered mixture. + filtering.gases[gas] = 0 + GAS_GARBAGE_COLLECT(filtering.gases) + + 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(. & EMP_PROTECT_SELF) + return + 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 = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.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, 435, 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) + + data["id_tag"] = -1 //must be defined in order to reuse code between portable and vent scrubbers + data["filter_types"] = list() + for(var/path in GLOB.meta_gas_ids) + data["filter_types"] += list(list("gas_id" = GLOB.meta_gas_ids[path], "gas_name" = GLOB.meta_gas_names[path], "enabled" = (path in scrubbing))) + + 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.forceMove(drop_location()) + holding = null + . = TRUE + if("toggle_filter") + scrubbing ^= gas_id2path(params["val"]) + . = 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 = on ? ACTIVE_POWER_USE : IDLE_POWER_USE + 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/W, mob/user) + if(default_unfasten_wrench(user, W)) + if(!movable) + on = FALSE + else + return ..() diff --git a/code/modules/cargo/bounties/engineering.dm b/code/modules/cargo/bounties/engineering.dm index eb1764d482..e10d48f3bf 100644 --- a/code/modules/cargo/bounties/engineering.dm +++ b/code/modules/cargo/bounties/engineering.dm @@ -12,7 +12,7 @@ var/obj/item/tank/T = O if(!T.air_contents.gases[gas_type]) return FALSE - return T.air_contents.gases[gas_type][MOLES] >= moles_required + return T.air_contents.gases[gas_type] >= moles_required /datum/bounty/item/engineering/gas/nitryl_tank name = "Full Tank of Nitryl" diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm index 966d5cc0d8..d8906de036 100644 --- a/code/modules/events/spacevine.dm +++ b/code/modules/events/spacevine.dm @@ -1,548 +1,548 @@ -/datum/round_event_control/spacevine - name = "Spacevine" - typepath = /datum/round_event/spacevine - weight = 15 - max_occurrences = 3 - min_players = 10 - -/datum/round_event/spacevine - fakeable = FALSE - -/datum/round_event/spacevine/start() - var/list/turfs = list() //list of all the empty floor turfs in the hallway areas - - var/obj/structure/spacevine/SV = new() - - for(var/area/hallway/A in world) - for(var/turf/F in A) - if(F.Enter(SV)) - turfs += F - - qdel(SV) - - if(turfs.len) //Pick a turf to spawn at if we can - var/turf/T = pick(turfs) - new /datum/spacevine_controller(T) //spawn a controller at turf - - -/datum/spacevine_mutation - var/name = "" - var/severity = 1 - var/hue - var/quality - -/datum/spacevine_mutation/proc/add_mutation_to_vinepiece(obj/structure/spacevine/holder) - holder.mutations |= src - holder.add_atom_colour(hue, FIXED_COLOUR_PRIORITY) - -/datum/spacevine_mutation/proc/process_mutation(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/process_temperature(obj/structure/spacevine/holder, temp, volume) - return - -/datum/spacevine_mutation/proc/on_birth(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_grow(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_death(obj/structure/spacevine/holder) - return - -/datum/spacevine_mutation/proc/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) - . = expected_damage - -/datum/spacevine_mutation/proc/on_cross(obj/structure/spacevine/holder, mob/crosser) - return - -/datum/spacevine_mutation/proc/on_chem(obj/structure/spacevine/holder, datum/reagent/R) - return - -/datum/spacevine_mutation/proc/on_eat(obj/structure/spacevine/holder, mob/living/eater) - return - -/datum/spacevine_mutation/proc/on_spread(obj/structure/spacevine/holder, turf/target) - return - -/datum/spacevine_mutation/proc/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) - return - -/datum/spacevine_mutation/proc/on_explosion(severity, target, obj/structure/spacevine/holder) - return - - -/datum/spacevine_mutation/light - name = "light" - hue = "#ffff00" - quality = POSITIVE - severity = 4 - -/datum/spacevine_mutation/light/on_grow(obj/structure/spacevine/holder) - if(holder.energy) - holder.set_light(severity, 0.3) - -/datum/spacevine_mutation/toxicity - name = "toxic" - hue = "#ff00ff" - severity = 10 - quality = NEGATIVE - -/datum/spacevine_mutation/toxicity/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(issilicon(crosser)) - return - if(prob(severity) && istype(crosser) && !isvineimmune(crosser)) - to_chat(crosser, "You accidentally touch the vine and feel a strange sensation.") - crosser.adjustToxLoss(5) - -/datum/spacevine_mutation/toxicity/on_eat(obj/structure/spacevine/holder, mob/living/eater) - if(!isvineimmune(eater)) - eater.adjustToxLoss(5) - -/datum/spacevine_mutation/explosive //OH SHIT IT CAN CHAINREACT RUN!!! - name = "explosive" - hue = "#ff0000" - quality = NEGATIVE - severity = 2 - -/datum/spacevine_mutation/explosive/on_explosion(explosion_severity, target, obj/structure/spacevine/holder) - if(explosion_severity < 3) - qdel(holder) - else - . = 1 - QDEL_IN(holder, 5) - -/datum/spacevine_mutation/explosive/on_death(obj/structure/spacevine/holder, mob/hitter, obj/item/I) - explosion(holder.loc, 0, 0, severity, 0, 0) - -/datum/spacevine_mutation/fire_proof - name = "fire proof" - hue = "#ff8888" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/fire_proof/process_temperature(obj/structure/spacevine/holder, temp, volume) - return 1 - -/datum/spacevine_mutation/fire_proof/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) - if(I && I.damtype == "fire") - . = 0 - else - . = expected_damage - -/datum/spacevine_mutation/vine_eating - name = "vine eating" - hue = "#ff7700" - quality = MINOR_NEGATIVE - -/datum/spacevine_mutation/vine_eating/on_spread(obj/structure/spacevine/holder, turf/target) - var/obj/structure/spacevine/prey = locate() in target - if(prey && !prey.mutations.Find(src)) //Eat all vines that are not of the same origin - qdel(prey) - -/datum/spacevine_mutation/aggressive_spread //very OP, but im out of other ideas currently - name = "aggressive spreading" - hue = "#333333" - severity = 3 - quality = NEGATIVE - -/datum/spacevine_mutation/aggressive_spread/on_spread(obj/structure/spacevine/holder, turf/target) - target.ex_act(severity, null, src) // vine immunity handled at /mob/ex_act - -/datum/spacevine_mutation/aggressive_spread/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) - buckled.ex_act(severity, null, src) - -/datum/spacevine_mutation/transparency - name = "transparent" - hue = "" - quality = POSITIVE - -/datum/spacevine_mutation/transparency/on_grow(obj/structure/spacevine/holder) - holder.set_opacity(0) - holder.alpha = 125 - -/datum/spacevine_mutation/oxy_eater - name = "oxygen consuming" - hue = "#ffff88" - severity = 3 - quality = NEGATIVE - -/datum/spacevine_mutation/oxy_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/oxygen]) - return - GM.gases[/datum/gas/oxygen][MOLES] = max(GM.gases[/datum/gas/oxygen][MOLES] - severity * holder.energy, 0) - GM.garbage_collect() - -/datum/spacevine_mutation/nitro_eater - name = "nitrogen consuming" - hue = "#8888ff" - severity = 3 - quality = NEGATIVE - -/datum/spacevine_mutation/nitro_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/nitrogen]) - return - GM.gases[/datum/gas/nitrogen][MOLES] = max(GM.gases[/datum/gas/nitrogen][MOLES] - severity * holder.energy, 0) - GM.garbage_collect() - -/datum/spacevine_mutation/carbondioxide_eater - name = "CO2 consuming" - hue = "#00ffff" - severity = 3 - quality = POSITIVE - -/datum/spacevine_mutation/carbondioxide_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/carbon_dioxide]) - return - GM.gases[/datum/gas/carbon_dioxide][MOLES] = max(GM.gases[/datum/gas/carbon_dioxide][MOLES] - severity * holder.energy, 0) - GM.garbage_collect() - -/datum/spacevine_mutation/plasma_eater - name = "toxins consuming" - hue = "#ffbbff" - severity = 3 - quality = POSITIVE - -/datum/spacevine_mutation/plasma_eater/process_mutation(obj/structure/spacevine/holder) - var/turf/open/floor/T = holder.loc - if(istype(T)) - var/datum/gas_mixture/GM = T.air - if(!GM.gases[/datum/gas/plasma]) - return - GM.gases[/datum/gas/plasma][MOLES] = max(GM.gases[/datum/gas/plasma][MOLES] - severity * holder.energy, 0) - GM.garbage_collect() - -/datum/spacevine_mutation/thorns - name = "thorny" - hue = "#666666" - severity = 10 - quality = NEGATIVE - -/datum/spacevine_mutation/thorns/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(prob(severity) && istype(crosser) && !isvineimmune(holder)) - var/mob/living/M = crosser - M.adjustBruteLoss(5) - to_chat(M, "You cut yourself on the thorny vines.") - -/datum/spacevine_mutation/thorns/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) - if(prob(severity) && istype(hitter) && !isvineimmune(holder)) - var/mob/living/M = hitter - M.adjustBruteLoss(5) - to_chat(M, "You cut yourself on the thorny vines.") - . = expected_damage - -/datum/spacevine_mutation/woodening - name = "hardened" - hue = "#997700" - quality = NEGATIVE - -/datum/spacevine_mutation/woodening/on_grow(obj/structure/spacevine/holder) - if(holder.energy) - holder.density = TRUE - holder.max_integrity = 100 - holder.obj_integrity = holder.max_integrity - -/datum/spacevine_mutation/woodening/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) - if(I.is_sharp()) - . = expected_damage * 0.5 - else - . = expected_damage - -/datum/spacevine_mutation/flowering - name = "flowering" - hue = "#0A480D" - quality = NEGATIVE - severity = 10 - -/datum/spacevine_mutation/flowering/on_grow(obj/structure/spacevine/holder) - if(holder.energy == 2 && prob(severity) && !locate(/obj/structure/alien/resin/flower_bud_enemy) in range(5,holder)) - new/obj/structure/alien/resin/flower_bud_enemy(get_turf(holder)) - -/datum/spacevine_mutation/flowering/on_cross(obj/structure/spacevine/holder, mob/living/crosser) - if(prob(25)) - holder.entangle(crosser) - - -// SPACE VINES (Note that this code is very similar to Biomass code) -/obj/structure/spacevine - name = "space vines" - desc = "An extremely expansionistic species of vine." - icon = 'icons/effects/spacevines.dmi' - icon_state = "Light1" - anchored = TRUE - density = FALSE - layer = SPACEVINE_LAYER - mouse_opacity = MOUSE_OPACITY_OPAQUE //Clicking anywhere on the turf is good enough - pass_flags = PASSTABLE | PASSGRILLE - max_integrity = 50 - var/energy = 0 - var/datum/spacevine_controller/master = null - var/list/mutations = list() - -/obj/structure/spacevine/Initialize() - . = ..() - add_atom_colour("#ffffff", FIXED_COLOUR_PRIORITY) - -/obj/structure/spacevine/examine(mob/user) - ..() - var/text = "This one is a" - if(mutations.len) - for(var/A in mutations) - var/datum/spacevine_mutation/SM = A - text += " [SM.name]" - else - text += " normal" - text += " vine." - to_chat(user, text) - -/obj/structure/spacevine/Destroy() - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_death(src) - if(master) - master.VineDestroyed(src) - mutations = list() - set_opacity(0) - if(has_buckled_mobs()) - unbuckle_all_mobs(force=1) - return ..() - -/obj/structure/spacevine/proc/on_chem_effect(datum/reagent/R) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.on_chem(src, R) - if(!override && istype(R, /datum/reagent/toxin/plantbgone)) - if(prob(50)) - qdel(src) - -/obj/structure/spacevine/proc/eat(mob/eater) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.on_eat(src, eater) - if(!override) - qdel(src) - -/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user) - var/damage_dealt = I.force - if(I.is_sharp()) - damage_dealt *= 4 - if(I.damtype == BURN) - damage_dealt *= 4 - - for(var/datum/spacevine_mutation/SM in mutations) - damage_dealt = SM.on_hit(src, user, I, damage_dealt) //on_hit now takes override damage as arg and returns new value for other mutations to permutate further - take_damage(damage_dealt, I.damtype, "melee", 1) - -/obj/structure/spacevine/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) - switch(damage_type) - if(BRUTE) - if(damage_amount) - playsound(src, 'sound/weapons/slash.ogg', 50, 1) - else - playsound(src, 'sound/weapons/tap.ogg', 50, 1) - if(BURN) - playsound(src.loc, 'sound/items/welder.ogg', 100, 1) - -/obj/structure/spacevine/Crossed(mob/crosser) - if(isliving(crosser)) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_cross(src, crosser) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/structure/spacevine/attack_hand(mob/user) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_hit(src, user) - user_unbuckle_mob(user, user) - . = ..() - -/obj/structure/spacevine/attack_paw(mob/living/user) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_hit(src, user) - user_unbuckle_mob(user,user) - -/obj/structure/spacevine/attack_alien(mob/living/user) - eat(user) - -/datum/spacevine_controller - var/list/obj/structure/spacevine/vines - var/list/growth_queue - var/spread_multiplier = 5 - var/spread_cap = 30 - var/list/vine_mutations_list - var/mutativeness = 1 - -/datum/spacevine_controller/New(turf/location, list/muts, potency, production) - vines = list() - growth_queue = list() - spawn_spacevine_piece(location, null, muts) - START_PROCESSING(SSobj, src) - vine_mutations_list = list() - init_subtypes(/datum/spacevine_mutation/, vine_mutations_list) - if(potency != null) - mutativeness = potency / 10 - if(production != null) - spread_cap *= production / 5 - spread_multiplier /= production / 5 - -/datum/spacevine_controller/vv_get_dropdown() - . = ..() - . += "---" - .["Delete Vines"] = "?_src_=[REF(src)];[HrefToken()];purge_vines=1" - -/datum/spacevine_controller/Topic(href, href_list) - if(..() || !check_rights(R_ADMIN, FALSE) || !usr.client.holder.CheckAdminHref(href, href_list)) - return - - if(href_list["purge_vines"]) - if(alert(usr, "Are you sure you want to delete this spacevine cluster?", "Delete Vines", "Yes", "No") != "Yes") - return - DeleteVines() - -/datum/spacevine_controller/proc/DeleteVines() //this is kill - QDEL_LIST(vines) //this will also qdel us - -/datum/spacevine_controller/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/datum/spacevine_controller/proc/spawn_spacevine_piece(turf/location, obj/structure/spacevine/parent, list/muts) - var/obj/structure/spacevine/SV = new(location) - growth_queue += SV - vines += SV - SV.master = src - if(muts && muts.len) - for(var/datum/spacevine_mutation/M in muts) - M.add_mutation_to_vinepiece(SV) - return - if(parent) - SV.mutations |= parent.mutations - var/parentcolor = parent.atom_colours[FIXED_COLOUR_PRIORITY] - SV.add_atom_colour(parentcolor, FIXED_COLOUR_PRIORITY) - if(prob(mutativeness)) - var/datum/spacevine_mutation/randmut = pick(vine_mutations_list - SV.mutations) - randmut.add_mutation_to_vinepiece(SV) - - for(var/datum/spacevine_mutation/SM in SV.mutations) - SM.on_birth(SV) - location.Entered(SV) - -/datum/spacevine_controller/proc/VineDestroyed(obj/structure/spacevine/S) - S.master = null - vines -= S - growth_queue -= S - if(!vines.len) - var/obj/item/seeds/kudzu/KZ = new(S.loc) - KZ.mutations |= S.mutations - KZ.set_potency(mutativeness * 10) - KZ.set_production((spread_cap / initial(spread_cap)) * 5) - qdel(src) - -/datum/spacevine_controller/process() - if(!LAZYLEN(vines)) - qdel(src) //space vines exterminated. Remove the controller - return - if(!growth_queue) - qdel(src) //Sanity check - return - - var/length = 0 - - length = min( spread_cap , max( 1 , vines.len / spread_multiplier ) ) - var/i = 0 - var/list/obj/structure/spacevine/queue_end = list() - - for(var/obj/structure/spacevine/SV in growth_queue) - if(QDELETED(SV)) - continue - i++ - queue_end += SV - growth_queue -= SV - for(var/datum/spacevine_mutation/SM in SV.mutations) - SM.process_mutation(SV) - if(SV.energy < 2) //If tile isn't fully grown - if(prob(20)) - SV.grow() - else //If tile is fully grown - SV.entangle_mob() - - SV.spread() - if(i >= length) - break - - growth_queue = growth_queue + queue_end - -/obj/structure/spacevine/proc/grow() - if(!energy) - src.icon_state = pick("Med1", "Med2", "Med3") - energy = 1 - set_opacity(1) - else - src.icon_state = pick("Hvy1", "Hvy2", "Hvy3") - energy = 2 - - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_grow(src) - -/obj/structure/spacevine/proc/entangle_mob() - if(!has_buckled_mobs() && prob(25)) - for(var/mob/living/V in src.loc) - entangle(V) - if(has_buckled_mobs()) - break //only capture one mob at a time - - -/obj/structure/spacevine/proc/entangle(mob/living/V) - if(!V || isvineimmune(V)) - return - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_buckle(src, V) - if((V.stat != DEAD) && (V.buckled != src)) //not dead or captured - to_chat(V, "The vines [pick("wind", "tangle", "tighten")] around you!") - buckle_mob(V, 1) - -/obj/structure/spacevine/proc/spread() - var/direction = pick(GLOB.cardinals) - var/turf/stepturf = get_step(src,direction) - if (!isspaceturf(stepturf) && stepturf.Enter(src)) - for(var/datum/spacevine_mutation/SM in mutations) - SM.on_spread(src, stepturf) - stepturf = get_step(src,direction) //in case turf changes, to make sure no runtimes happen - if(!locate(/obj/structure/spacevine, stepturf)) - if(master) - master.spawn_spacevine_piece(stepturf, src) - -/obj/structure/spacevine/ex_act(severity, target) - if(istype(target, type)) //if its agressive spread vine dont do anything - return - var/i - for(var/datum/spacevine_mutation/SM in mutations) - i += SM.on_explosion(severity, target, src) - if(!i && prob(100/severity)) - qdel(src) - -/obj/structure/spacevine/temperature_expose(null, temp, volume) - var/override = 0 - for(var/datum/spacevine_mutation/SM in mutations) - override += SM.process_temperature(src, temp, volume) - if(!override) - qdel(src) - -/obj/structure/spacevine/CanPass(atom/movable/mover, turf/target) - if(isvineimmune(mover)) - . = TRUE - else - . = ..() - -/proc/isvineimmune(atom/A) - . = FALSE - if(isliving(A)) - var/mob/living/M = A - if(("vines" in M.faction) || ("plants" in M.faction)) - . = TRUE +/datum/round_event_control/spacevine + name = "Spacevine" + typepath = /datum/round_event/spacevine + weight = 15 + max_occurrences = 3 + min_players = 10 + +/datum/round_event/spacevine + fakeable = FALSE + +/datum/round_event/spacevine/start() + var/list/turfs = list() //list of all the empty floor turfs in the hallway areas + + var/obj/structure/spacevine/SV = new() + + for(var/area/hallway/A in world) + for(var/turf/F in A) + if(F.Enter(SV)) + turfs += F + + qdel(SV) + + if(turfs.len) //Pick a turf to spawn at if we can + var/turf/T = pick(turfs) + new /datum/spacevine_controller(T) //spawn a controller at turf + + +/datum/spacevine_mutation + var/name = "" + var/severity = 1 + var/hue + var/quality + +/datum/spacevine_mutation/proc/add_mutation_to_vinepiece(obj/structure/spacevine/holder) + holder.mutations |= src + holder.add_atom_colour(hue, FIXED_COLOUR_PRIORITY) + +/datum/spacevine_mutation/proc/process_mutation(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/process_temperature(obj/structure/spacevine/holder, temp, volume) + return + +/datum/spacevine_mutation/proc/on_birth(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_grow(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_death(obj/structure/spacevine/holder) + return + +/datum/spacevine_mutation/proc/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) + . = expected_damage + +/datum/spacevine_mutation/proc/on_cross(obj/structure/spacevine/holder, mob/crosser) + return + +/datum/spacevine_mutation/proc/on_chem(obj/structure/spacevine/holder, datum/reagent/R) + return + +/datum/spacevine_mutation/proc/on_eat(obj/structure/spacevine/holder, mob/living/eater) + return + +/datum/spacevine_mutation/proc/on_spread(obj/structure/spacevine/holder, turf/target) + return + +/datum/spacevine_mutation/proc/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) + return + +/datum/spacevine_mutation/proc/on_explosion(severity, target, obj/structure/spacevine/holder) + return + + +/datum/spacevine_mutation/light + name = "light" + hue = "#ffff00" + quality = POSITIVE + severity = 4 + +/datum/spacevine_mutation/light/on_grow(obj/structure/spacevine/holder) + if(holder.energy) + holder.set_light(severity, 0.3) + +/datum/spacevine_mutation/toxicity + name = "toxic" + hue = "#ff00ff" + severity = 10 + quality = NEGATIVE + +/datum/spacevine_mutation/toxicity/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(issilicon(crosser)) + return + if(prob(severity) && istype(crosser) && !isvineimmune(crosser)) + to_chat(crosser, "You accidentally touch the vine and feel a strange sensation.") + crosser.adjustToxLoss(5) + +/datum/spacevine_mutation/toxicity/on_eat(obj/structure/spacevine/holder, mob/living/eater) + if(!isvineimmune(eater)) + eater.adjustToxLoss(5) + +/datum/spacevine_mutation/explosive //OH SHIT IT CAN CHAINREACT RUN!!! + name = "explosive" + hue = "#ff0000" + quality = NEGATIVE + severity = 2 + +/datum/spacevine_mutation/explosive/on_explosion(explosion_severity, target, obj/structure/spacevine/holder) + if(explosion_severity < 3) + qdel(holder) + else + . = 1 + QDEL_IN(holder, 5) + +/datum/spacevine_mutation/explosive/on_death(obj/structure/spacevine/holder, mob/hitter, obj/item/I) + explosion(holder.loc, 0, 0, severity, 0, 0) + +/datum/spacevine_mutation/fire_proof + name = "fire proof" + hue = "#ff8888" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/fire_proof/process_temperature(obj/structure/spacevine/holder, temp, volume) + return 1 + +/datum/spacevine_mutation/fire_proof/on_hit(obj/structure/spacevine/holder, mob/hitter, obj/item/I, expected_damage) + if(I && I.damtype == "fire") + . = 0 + else + . = expected_damage + +/datum/spacevine_mutation/vine_eating + name = "vine eating" + hue = "#ff7700" + quality = MINOR_NEGATIVE + +/datum/spacevine_mutation/vine_eating/on_spread(obj/structure/spacevine/holder, turf/target) + var/obj/structure/spacevine/prey = locate() in target + if(prey && !prey.mutations.Find(src)) //Eat all vines that are not of the same origin + qdel(prey) + +/datum/spacevine_mutation/aggressive_spread //very OP, but im out of other ideas currently + name = "aggressive spreading" + hue = "#333333" + severity = 3 + quality = NEGATIVE + +/datum/spacevine_mutation/aggressive_spread/on_spread(obj/structure/spacevine/holder, turf/target) + target.ex_act(severity, null, src) // vine immunity handled at /mob/ex_act + +/datum/spacevine_mutation/aggressive_spread/on_buckle(obj/structure/spacevine/holder, mob/living/buckled) + buckled.ex_act(severity, null, src) + +/datum/spacevine_mutation/transparency + name = "transparent" + hue = "" + quality = POSITIVE + +/datum/spacevine_mutation/transparency/on_grow(obj/structure/spacevine/holder) + holder.set_opacity(0) + holder.alpha = 125 + +/datum/spacevine_mutation/oxy_eater + name = "oxygen consuming" + hue = "#ffff88" + severity = 3 + quality = NEGATIVE + +/datum/spacevine_mutation/oxy_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/oxygen]) + return + GM.gases[/datum/gas/oxygen] = max(GM.gases[/datum/gas/oxygen] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/nitro_eater + name = "nitrogen consuming" + hue = "#8888ff" + severity = 3 + quality = NEGATIVE + +/datum/spacevine_mutation/nitro_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/nitrogen]) + return + GM.gases[/datum/gas/nitrogen] = max(GM.gases[/datum/gas/nitrogen] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/carbondioxide_eater + name = "CO2 consuming" + hue = "#00ffff" + severity = 3 + quality = POSITIVE + +/datum/spacevine_mutation/carbondioxide_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/carbon_dioxide]) + return + GM.gases[/datum/gas/carbon_dioxide] = max(GM.gases[/datum/gas/carbon_dioxide] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/plasma_eater + name = "toxins consuming" + hue = "#ffbbff" + severity = 3 + quality = POSITIVE + +/datum/spacevine_mutation/plasma_eater/process_mutation(obj/structure/spacevine/holder) + var/turf/open/floor/T = holder.loc + if(istype(T)) + var/datum/gas_mixture/GM = T.air + if(!GM.gases[/datum/gas/plasma]) + return + GM.gases[/datum/gas/plasma] = max(GM.gases[/datum/gas/plasma] - severity * holder.energy, 0) + GAS_GARBAGE_COLLECT(GM.gases) + +/datum/spacevine_mutation/thorns + name = "thorny" + hue = "#666666" + severity = 10 + quality = NEGATIVE + +/datum/spacevine_mutation/thorns/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(prob(severity) && istype(crosser) && !isvineimmune(holder)) + var/mob/living/M = crosser + M.adjustBruteLoss(5) + to_chat(M, "You cut yourself on the thorny vines.") + +/datum/spacevine_mutation/thorns/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) + if(prob(severity) && istype(hitter) && !isvineimmune(holder)) + var/mob/living/M = hitter + M.adjustBruteLoss(5) + to_chat(M, "You cut yourself on the thorny vines.") + . = expected_damage + +/datum/spacevine_mutation/woodening + name = "hardened" + hue = "#997700" + quality = NEGATIVE + +/datum/spacevine_mutation/woodening/on_grow(obj/structure/spacevine/holder) + if(holder.energy) + holder.density = TRUE + holder.max_integrity = 100 + holder.obj_integrity = holder.max_integrity + +/datum/spacevine_mutation/woodening/on_hit(obj/structure/spacevine/holder, mob/living/hitter, obj/item/I, expected_damage) + if(I.is_sharp()) + . = expected_damage * 0.5 + else + . = expected_damage + +/datum/spacevine_mutation/flowering + name = "flowering" + hue = "#0A480D" + quality = NEGATIVE + severity = 10 + +/datum/spacevine_mutation/flowering/on_grow(obj/structure/spacevine/holder) + if(holder.energy == 2 && prob(severity) && !locate(/obj/structure/alien/resin/flower_bud_enemy) in range(5,holder)) + new/obj/structure/alien/resin/flower_bud_enemy(get_turf(holder)) + +/datum/spacevine_mutation/flowering/on_cross(obj/structure/spacevine/holder, mob/living/crosser) + if(prob(25)) + holder.entangle(crosser) + + +// SPACE VINES (Note that this code is very similar to Biomass code) +/obj/structure/spacevine + name = "space vines" + desc = "An extremely expansionistic species of vine." + icon = 'icons/effects/spacevines.dmi' + icon_state = "Light1" + anchored = TRUE + density = FALSE + layer = SPACEVINE_LAYER + mouse_opacity = MOUSE_OPACITY_OPAQUE //Clicking anywhere on the turf is good enough + pass_flags = PASSTABLE | PASSGRILLE + max_integrity = 50 + var/energy = 0 + var/datum/spacevine_controller/master = null + var/list/mutations = list() + +/obj/structure/spacevine/Initialize() + . = ..() + add_atom_colour("#ffffff", FIXED_COLOUR_PRIORITY) + +/obj/structure/spacevine/examine(mob/user) + ..() + var/text = "This one is a" + if(mutations.len) + for(var/A in mutations) + var/datum/spacevine_mutation/SM = A + text += " [SM.name]" + else + text += " normal" + text += " vine." + to_chat(user, text) + +/obj/structure/spacevine/Destroy() + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_death(src) + if(master) + master.VineDestroyed(src) + mutations = list() + set_opacity(0) + if(has_buckled_mobs()) + unbuckle_all_mobs(force=1) + return ..() + +/obj/structure/spacevine/proc/on_chem_effect(datum/reagent/R) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.on_chem(src, R) + if(!override && istype(R, /datum/reagent/toxin/plantbgone)) + if(prob(50)) + qdel(src) + +/obj/structure/spacevine/proc/eat(mob/eater) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.on_eat(src, eater) + if(!override) + qdel(src) + +/obj/structure/spacevine/attacked_by(obj/item/I, mob/living/user) + var/damage_dealt = I.force + if(I.is_sharp()) + damage_dealt *= 4 + if(I.damtype == BURN) + damage_dealt *= 4 + + for(var/datum/spacevine_mutation/SM in mutations) + damage_dealt = SM.on_hit(src, user, I, damage_dealt) //on_hit now takes override damage as arg and returns new value for other mutations to permutate further + take_damage(damage_dealt, I.damtype, "melee", 1) + +/obj/structure/spacevine/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(src, 'sound/weapons/slash.ogg', 50, 1) + else + playsound(src, 'sound/weapons/tap.ogg', 50, 1) + if(BURN) + playsound(src.loc, 'sound/items/welder.ogg', 100, 1) + +/obj/structure/spacevine/Crossed(mob/crosser) + if(isliving(crosser)) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_cross(src, crosser) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/structure/spacevine/attack_hand(mob/user) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_hit(src, user) + user_unbuckle_mob(user, user) + . = ..() + +/obj/structure/spacevine/attack_paw(mob/living/user) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_hit(src, user) + user_unbuckle_mob(user,user) + +/obj/structure/spacevine/attack_alien(mob/living/user) + eat(user) + +/datum/spacevine_controller + var/list/obj/structure/spacevine/vines + var/list/growth_queue + var/spread_multiplier = 5 + var/spread_cap = 30 + var/list/vine_mutations_list + var/mutativeness = 1 + +/datum/spacevine_controller/New(turf/location, list/muts, potency, production) + vines = list() + growth_queue = list() + spawn_spacevine_piece(location, null, muts) + START_PROCESSING(SSobj, src) + vine_mutations_list = list() + init_subtypes(/datum/spacevine_mutation/, vine_mutations_list) + if(potency != null) + mutativeness = potency / 10 + if(production != null) + spread_cap *= production / 5 + spread_multiplier /= production / 5 + +/datum/spacevine_controller/vv_get_dropdown() + . = ..() + . += "---" + .["Delete Vines"] = "?_src_=[REF(src)];[HrefToken()];purge_vines=1" + +/datum/spacevine_controller/Topic(href, href_list) + if(..() || !check_rights(R_ADMIN, FALSE) || !usr.client.holder.CheckAdminHref(href, href_list)) + return + + if(href_list["purge_vines"]) + if(alert(usr, "Are you sure you want to delete this spacevine cluster?", "Delete Vines", "Yes", "No") != "Yes") + return + DeleteVines() + +/datum/spacevine_controller/proc/DeleteVines() //this is kill + QDEL_LIST(vines) //this will also qdel us + +/datum/spacevine_controller/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/datum/spacevine_controller/proc/spawn_spacevine_piece(turf/location, obj/structure/spacevine/parent, list/muts) + var/obj/structure/spacevine/SV = new(location) + growth_queue += SV + vines += SV + SV.master = src + if(muts && muts.len) + for(var/datum/spacevine_mutation/M in muts) + M.add_mutation_to_vinepiece(SV) + return + if(parent) + SV.mutations |= parent.mutations + var/parentcolor = parent.atom_colours[FIXED_COLOUR_PRIORITY] + SV.add_atom_colour(parentcolor, FIXED_COLOUR_PRIORITY) + if(prob(mutativeness)) + var/datum/spacevine_mutation/randmut = pick(vine_mutations_list - SV.mutations) + randmut.add_mutation_to_vinepiece(SV) + + for(var/datum/spacevine_mutation/SM in SV.mutations) + SM.on_birth(SV) + location.Entered(SV) + +/datum/spacevine_controller/proc/VineDestroyed(obj/structure/spacevine/S) + S.master = null + vines -= S + growth_queue -= S + if(!vines.len) + var/obj/item/seeds/kudzu/KZ = new(S.loc) + KZ.mutations |= S.mutations + KZ.set_potency(mutativeness * 10) + KZ.set_production((spread_cap / initial(spread_cap)) * 5) + qdel(src) + +/datum/spacevine_controller/process() + if(!LAZYLEN(vines)) + qdel(src) //space vines exterminated. Remove the controller + return + if(!growth_queue) + qdel(src) //Sanity check + return + + var/length = 0 + + length = min( spread_cap , max( 1 , vines.len / spread_multiplier ) ) + var/i = 0 + var/list/obj/structure/spacevine/queue_end = list() + + for(var/obj/structure/spacevine/SV in growth_queue) + if(QDELETED(SV)) + continue + i++ + queue_end += SV + growth_queue -= SV + for(var/datum/spacevine_mutation/SM in SV.mutations) + SM.process_mutation(SV) + if(SV.energy < 2) //If tile isn't fully grown + if(prob(20)) + SV.grow() + else //If tile is fully grown + SV.entangle_mob() + + SV.spread() + if(i >= length) + break + + growth_queue = growth_queue + queue_end + +/obj/structure/spacevine/proc/grow() + if(!energy) + src.icon_state = pick("Med1", "Med2", "Med3") + energy = 1 + set_opacity(1) + else + src.icon_state = pick("Hvy1", "Hvy2", "Hvy3") + energy = 2 + + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_grow(src) + +/obj/structure/spacevine/proc/entangle_mob() + if(!has_buckled_mobs() && prob(25)) + for(var/mob/living/V in src.loc) + entangle(V) + if(has_buckled_mobs()) + break //only capture one mob at a time + + +/obj/structure/spacevine/proc/entangle(mob/living/V) + if(!V || isvineimmune(V)) + return + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_buckle(src, V) + if((V.stat != DEAD) && (V.buckled != src)) //not dead or captured + to_chat(V, "The vines [pick("wind", "tangle", "tighten")] around you!") + buckle_mob(V, 1) + +/obj/structure/spacevine/proc/spread() + var/direction = pick(GLOB.cardinals) + var/turf/stepturf = get_step(src,direction) + if (!isspaceturf(stepturf) && stepturf.Enter(src)) + for(var/datum/spacevine_mutation/SM in mutations) + SM.on_spread(src, stepturf) + stepturf = get_step(src,direction) //in case turf changes, to make sure no runtimes happen + if(!locate(/obj/structure/spacevine, stepturf)) + if(master) + master.spawn_spacevine_piece(stepturf, src) + +/obj/structure/spacevine/ex_act(severity, target) + if(istype(target, type)) //if its agressive spread vine dont do anything + return + var/i + for(var/datum/spacevine_mutation/SM in mutations) + i += SM.on_explosion(severity, target, src) + if(!i && prob(100/severity)) + qdel(src) + +/obj/structure/spacevine/temperature_expose(null, temp, volume) + var/override = 0 + for(var/datum/spacevine_mutation/SM in mutations) + override += SM.process_temperature(src, temp, volume) + if(!override) + qdel(src) + +/obj/structure/spacevine/CanPass(atom/movable/mover, turf/target) + if(isvineimmune(mover)) + . = TRUE + else + . = ..() + +/proc/isvineimmune(atom/A) + . = FALSE + if(isliving(A)) + var/mob/living/M = A + if(("vines" in M.faction) || ("plants" in M.faction)) + . = TRUE diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm index 3fe5a2dfca..23f178edc9 100644 --- a/code/modules/hydroponics/grown/towercap.dm +++ b/code/modules/hydroponics/grown/towercap.dm @@ -187,7 +187,7 @@ var/turf/open/O = loc if(O.air) var/loc_gases = O.air.gases - if(loc_gases[/datum/gas/oxygen][MOLES] > 13) + if(loc_gases[/datum/gas/oxygen] > 13) return TRUE return FALSE diff --git a/code/modules/integrated_electronics/subtypes/atmospherics.dm b/code/modules/integrated_electronics/subtypes/atmospherics.dm index 1715223fa2..230d1ce154 100644 --- a/code/modules/integrated_electronics/subtypes/atmospherics.dm +++ b/code/modules/integrated_electronics/subtypes/atmospherics.dm @@ -1,762 +1,761 @@ -#define SOURCE_TO_TARGET 0 -#define TARGET_TO_SOURCE 1 -#define PUMP_EFFICIENCY 0.6 -#define TANK_FAILURE_PRESSURE (ONE_ATMOSPHERE*25) -#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE*24) -#define PUMP_MAX_VOLUME 100 - - -/obj/item/integrated_circuit/atmospherics - category_text = "Atmospherics" - cooldown_per_use = 2 SECONDS - complexity = 10 - size = 7 - outputs = list( - "self reference" = IC_PINTYPE_SELFREF, - "pressure" = IC_PINTYPE_NUMBER - ) - var/datum/gas_mixture/air_contents - var/volume = 2 //Pretty small, I know - -/obj/item/integrated_circuit/atmospherics/Initialize() - air_contents = new(volume) - ..() - -/obj/item/integrated_circuit/atmospherics/return_air() - return air_contents - -//Check if the gas container is adjacent and of the right type -/obj/item/integrated_circuit/atmospherics/proc/check_gassource(atom/gasholder) - if(!gasholder) - return FALSE - if(!gasholder.Adjacent(get_object())) - return FALSE - if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) - return FALSE - return TRUE - -//Needed in circuits where source and target types differ -/obj/item/integrated_circuit/atmospherics/proc/check_gastarget(atom/gasholder) - return check_gassource(gasholder) - - -// - gas pump - // **works** -/obj/item/integrated_circuit/atmospherics/pump - name = "gas pump" - desc = "Somehow moves gases between two tanks, canisters, and other gas containers." - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - inputs = list( - "source" = IC_PINTYPE_REF, - "target" = IC_PINTYPE_REF, - "target pressure" = IC_PINTYPE_NUMBER - ) - activators = list( - "transfer" = IC_PINTYPE_PULSE_IN, - "on transfer" = IC_PINTYPE_PULSE_OUT - ) - var/direction = SOURCE_TO_TARGET - var/target_pressure = PUMP_MAX_PRESSURE - power_draw_per_use = 20 - -/obj/item/integrated_circuit/atmospherics/pump/Initialize() - air_contents = new(volume) - extended_desc += " Use negative pressure to move air from target to source. \ - Note that only part of the gas is moved on each transfer, \ - so multiple activations will be necessary to achieve target pressure. \ - The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." - . = ..() - -// This proc gets the direction of the gas flow depending on its value, by calling update target -/obj/item/integrated_circuit/atmospherics/pump/on_data_written() - var/amt = get_pin_data(IC_INPUT, 3) - update_target(amt) - -/obj/item/integrated_circuit/atmospherics/pump/proc/update_target(new_amount) - if(!isnum(new_amount)) - new_amount = 0 - // See in which direction the gas moves - if(new_amount < 0) - direction = TARGET_TO_SOURCE - else - direction = SOURCE_TO_TARGET - target_pressure = min(round(PUMP_MAX_PRESSURE),abs(new_amount)) - -/obj/item/integrated_circuit/atmospherics/pump/do_work() - var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) - var/obj/target = get_pin_data_as_type(IC_INPUT, 2, /obj) - perform_magic(source, target) - activate_pin(2) - -/obj/item/integrated_circuit/atmospherics/pump/proc/perform_magic(atom/source, atom/target) - //Check if both atoms are of the right type: atmos circuits/gas tanks/canisters. If one is the same, use the circuit var - if(!check_gassource(source)) - source = src - - if(!check_gastarget(target)) - target = src - - // If both are the same, this whole proc would do nothing and just waste performance - if(source == target) - return - - var/datum/gas_mixture/source_air = source.return_air() - var/datum/gas_mixture/target_air = target.return_air() - - if(!source_air || !target_air) - return - - // Swapping both source and target - if(direction == TARGET_TO_SOURCE) - var/temp = source_air - source_air = target_air - target_air = temp - - // If what you are pumping is empty, use the circuit's storage - if(source_air.total_moles() <= 0) - source_air = air_contents - - // Move gas from one place to another - move_gas(source_air, target_air) - air_update_turf() - -/obj/item/integrated_circuit/atmospherics/pump/proc/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) - - // No moles = nothing to pump - if(source_air.total_moles() <= 0 || target_air.return_pressure() >= PUMP_MAX_PRESSURE) - return - - // Negative Kelvin temperatures should never happen and if they do, normalize them - if(source_air.temperature < TCMB) - source_air.temperature = TCMB - - var/pressure_delta = target_pressure - target_air.return_pressure() - if(pressure_delta > 0.1) - var/transfer_moles = (pressure_delta*target_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY - var/datum/gas_mixture/removed = source_air.remove(transfer_moles) - target_air.merge(removed) - - -// - volume pump - // **Works** -/obj/item/integrated_circuit/atmospherics/pump/volume - name = "volume pump" - desc = "Moves gases between two tanks, canisters, and other gas containers by using their volume, up to 200 L/s." - extended_desc = " Use negative volume to move air from target to source. Note that only part of the gas is moved on each transfer. Its maximum pumping volume is capped at 1000kPa." - - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - inputs = list( - "source" = IC_PINTYPE_REF, - "target" = IC_PINTYPE_REF, - "transfer volume" = IC_PINTYPE_NUMBER - ) - activators = list( - "transfer" = IC_PINTYPE_PULSE_IN, - "on transfer" = IC_PINTYPE_PULSE_OUT - ) - direction = SOURCE_TO_TARGET - var/transfer_rate = PUMP_MAX_VOLUME - power_draw_per_use = 20 - -/obj/item/integrated_circuit/atmospherics/pump/volume/update_target(new_amount) - if(!isnum(new_amount)) - new_amount = 0 - // See in which direction the gas moves - if(new_amount < 0) - direction = TARGET_TO_SOURCE - else - direction = SOURCE_TO_TARGET - target_pressure = min(PUMP_MAX_VOLUME,abs(new_amount)) - -/obj/item/integrated_circuit/atmospherics/pump/volume/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) - // No moles = nothing to pump - if(source_air.total_moles() <= 0) - return - - // Negative Kelvin temperatures should never happen and if they do, normalize them - if(source_air.temperature < TCMB) - source_air.temperature = TCMB - - if((source_air.return_pressure() < 0.01) || (target_air.return_pressure() >= PUMP_MAX_PRESSURE)) - return - - //The second part of the min caps the pressure built by the volume pumps to the max pump pressure - var/transfer_ratio = min(transfer_rate,target_air.volume*PUMP_MAX_PRESSURE/source_air.return_pressure())/source_air.volume - - var/datum/gas_mixture/removed = source_air.remove_ratio(transfer_ratio * PUMP_EFFICIENCY) - - target_air.merge(removed) - - -// - gas vent - // **works** -/obj/item/integrated_circuit/atmospherics/pump/vent - name = "gas vent" - extended_desc = "Use negative volume to move air from target to environment. Note that only part of the gas is moved on each transfer. Unlike the gas pump, this one keeps pumping even further to pressures of 9000 pKa and it is not advised to use it on tank circuits." - desc = "Moves gases between the environment and adjacent gas containers." - inputs = list( - "container" = IC_PINTYPE_REF, - "target pressure" = IC_PINTYPE_NUMBER - ) - -/obj/item/integrated_circuit/atmospherics/pump/vent/on_data_written() - var/amt = get_pin_data(IC_INPUT, 2) - update_target(amt) - -/obj/item/integrated_circuit/atmospherics/pump/vent/do_work() - var/turf/source = get_turf(src) - var/obj/target = get_pin_data_as_type(IC_INPUT, 1, /obj) - perform_magic(source, target) - activate_pin(2) - -/obj/item/integrated_circuit/atmospherics/pump/vent/check_gastarget(atom/gasholder) - if(!gasholder) - return FALSE - if(!gasholder.Adjacent(get_object())) - return FALSE - if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) - return FALSE - return TRUE - - -/obj/item/integrated_circuit/atmospherics/pump/vent/check_gassource(atom/target) - if(!target) - return FALSE - if(!istype(target, /turf)) - return FALSE - return TRUE - - -// - integrated connector - // Can connect and disconnect properly -/obj/item/integrated_circuit/atmospherics/connector - name = "integrated connector" - desc = "Creates an airtight seal with standard connectors found on the floor, \ - allowing the assembly to exchange gases with a pipe network." - extended_desc = "This circuit will automatically attempt to locate and connect to ports on the floor beneath it when activated. \ - You must set a target before connecting." - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - inputs = list( - "target" = IC_PINTYPE_REF - ) - activators = list( - "toggle connection" = IC_PINTYPE_PULSE_IN, - "on connected" = IC_PINTYPE_PULSE_OUT, - "on connection failed" = IC_PINTYPE_PULSE_OUT, - "on disconnected" = IC_PINTYPE_PULSE_OUT - ) - - var/obj/machinery/atmospherics/components/unary/portables_connector/connector - -/obj/item/integrated_circuit/atmospherics/connector/Initialize() - air_contents = new(volume) - START_PROCESSING(SSobj, src) - . = ..() - -//Sucks up the gas from the connector -/obj/item/integrated_circuit/atmospherics/connector/process() - set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) - -/obj/item/integrated_circuit/atmospherics/connector/check_gassource(atom/gasholder) - if(!gasholder) - return FALSE - if(!istype(gasholder,/obj/machinery/atmospherics/components/unary/portables_connector)) - return FALSE - return TRUE - -//If the assembly containing this is moved from the tile the connector pipe is in, the connection breaks -/obj/item/integrated_circuit/atmospherics/connector/ext_moved() - if(connector) - if(get_dist(get_object(), connector) > 0) - // The assembly is set as connected device and the connector handles the rest - connector.connected_device = null - connector = null - activate_pin(4) - -/obj/item/integrated_circuit/atmospherics/connector/do_work() - // If there is a connection, disconnect - if(connector) - connector.connected_device = null - connector = null - activate_pin(4) - return - - var/obj/machinery/atmospherics/components/unary/portables_connector/PC = locate() in get_turf(src) - // If no connector can't connect - if(!PC) - activate_pin(3) - return - connector = PC - connector.connected_device = src - activate_pin(2) - -// Required for making the connector port script work -obj/item/integrated_circuit/atmospherics/connector/portableConnectorReturnAir() - return air_contents - - -// - gas filter - // **works** -/obj/item/integrated_circuit/atmospherics/pump/filter - name = "gas filter" - desc = "Filters one gas out of a mixture." - complexity = 20 - size = 8 - spawn_flags = IC_SPAWN_RESEARCH - inputs = list( - "source" = IC_PINTYPE_REF, - "filtered output" = IC_PINTYPE_REF, - "contaminants output" = IC_PINTYPE_REF, - "wanted gases" = IC_PINTYPE_LIST, - "target pressure" = IC_PINTYPE_NUMBER - ) - power_draw_per_use = 30 - -/obj/item/integrated_circuit/atmospherics/pump/filter/on_data_written() - var/amt = get_pin_data(IC_INPUT, 5) - target_pressure = CLAMP(amt, 0, PUMP_MAX_PRESSURE) - -/obj/item/integrated_circuit/atmospherics/pump/filter/do_work() - activate_pin(2) - var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) - var/obj/filtered = get_pin_data_as_type(IC_INPUT, 2, /obj) - var/obj/contaminants = get_pin_data_as_type(IC_INPUT, 3, /obj) - - var/wanted = get_pin_data(IC_INPUT, 4) - - // If there is no filtered output, this whole thing makes no sense - if(!check_gassource(filtered)) - return - - var/datum/gas_mixture/filtered_air = filtered.return_air() - if(!filtered_air) - return - - // If no source is set, the source is possibly this circuit's content - if(!check_gassource(source)) - source = src - var/datum/gas_mixture/source_air = source.return_air() - - //No source air: source is this circuit - if(!source_air) - source_air = air_contents - - // If no filtering tank is set, filter through itself - if(!check_gassource(contaminants)) - contaminants = src - var/datum/gas_mixture/contaminated_air = contaminants.return_air() - - //If there is no gas mixture datum for unfiltered, pump the contaminants back into the circuit - if(!contaminated_air) - contaminated_air = air_contents - - if(contaminated_air.return_pressure() >= PUMP_MAX_PRESSURE || filtered_air.return_pressure() >= PUMP_MAX_PRESSURE) - return - - var/pressure_delta = target_pressure - contaminated_air.return_pressure() - var/transfer_moles - - //Negative Kelvins are an anomaly and should be normalized if encountered - if(source_air.temperature < TCMB) - source_air.temperature = TCMB - - transfer_moles = (pressure_delta*contaminated_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY - - //If there is nothing to transfer, just return - if(transfer_moles <= 0) - return - - //This is the var that holds the currently filtered part of the gas - var/datum/gas_mixture/removed = source_air.remove(transfer_moles) - if(!removed) - return - - //This is the gas that will be moved from source to filtered - var/datum/gas_mixture/filtered_out = new - - for(var/filtered_gas in removed.gases) - //Get the name of the gas and see if it is in the list - if(removed.gases[filtered_gas][GAS_META][META_GAS_NAME] in wanted) - //The gas that is put in all the filtered out gases - filtered_out.temperature = removed.temperature - filtered_out.add_gas(filtered_gas) - filtered_out.gases[filtered_gas][MOLES] = removed.gases[filtered_gas][MOLES] - - //The filtered out gas is entirely removed from the currently filtered gases - removed.gases[filtered_gas][MOLES] = 0 - removed.garbage_collect() - - //Check if the pressure is high enough to put stuff in filtered, or else just put it back in the source - var/datum/gas_mixture/target = (filtered_air.return_pressure() < target_pressure ? filtered_air : source_air) - target.merge(filtered_out) - contaminated_air.merge(removed) - - -/obj/item/integrated_circuit/atmospherics/pump/filter/Initialize() - air_contents = new(volume) - . = ..() - extended_desc = "Remember to properly spell and capitalize the filtered gas name. \ - Note that only part of the gas is moved on each transfer, \ - so multiple activations will be necessary to achieve target pressure. \ - The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." - - -// - gas mixer - // **works** -/obj/item/integrated_circuit/atmospherics/pump/mixer - name = "gas mixer" - desc = "Mixes 2 different types of gases." - complexity = 20 - size = 8 - spawn_flags = IC_SPAWN_RESEARCH - inputs = list( - "first source" = IC_PINTYPE_REF, - "second source" = IC_PINTYPE_REF, - "output" = IC_PINTYPE_REF, - "first source percentage" = IC_PINTYPE_NUMBER, - "target pressure" = IC_PINTYPE_NUMBER - ) - power_draw_per_use = 30 - -/obj/item/integrated_circuit/atmospherics/pump/mixer/do_work() - activate_pin(2) - var/obj/source_1 = get_pin_data(IC_INPUT, 1) - var/obj/source_2 = get_pin_data(IC_INPUT, 2) - var/obj/gas_output = get_pin_data(IC_INPUT, 3) - if(!check_gassource(source_1)) - source_1 = src - - if(!check_gassource(source_2)) - source_2 = src - - if(!check_gassource(gas_output)) - gas_output = src - - if(source_1 == gas_output || source_2 == gas_output) - return - - var/datum/gas_mixture/source_1_gases = source_1.return_air() - var/datum/gas_mixture/source_2_gases = source_2.return_air() - var/datum/gas_mixture/output_gases = gas_output.return_air() - - if(!source_1_gases || !source_2_gases || !output_gases) - return - - if(output_gases.return_pressure() >= PUMP_MAX_PRESSURE) - return - - if(source_1_gases.return_pressure() <= 0 || source_2_gases.return_pressure() <= 0) - return - - //This calculates how much should be sent - var/gas_percentage = round(max(min(get_pin_data(IC_INPUT, 4),100),0) / 100) - - //Basically: number of moles = percentage of pressure filled up * efficiency coefficient * (pressure from both gases * volume of output) / (R * Temperature) - var/transfer_moles = (get_pin_data(IC_INPUT, 5) / max(1,output_gases.return_pressure())) * PUMP_EFFICIENCY * (source_1_gases.return_pressure() * gas_percentage + source_2_gases.return_pressure() * (1 - gas_percentage)) * output_gases.volume/ (R_IDEAL_GAS_EQUATION * max(output_gases.temperature,TCMB)) - - - if(transfer_moles <= 0) - return - - var/datum/gas_mixture/mix = source_1_gases.remove(transfer_moles * gas_percentage) - output_gases.merge(mix) - mix = source_2_gases.remove(transfer_moles * (1-gas_percentage)) - output_gases.merge(mix) - - -// - integrated tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank - name = "integrated tank" - desc = "A small tank for the storage of gases." - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - size = 4 - activators = list( - "push ref" = IC_PINTYPE_PULSE_IN - ) - volume = 3 //emergency tank sized - var/broken = FALSE - -/obj/item/integrated_circuit/atmospherics/tank/Initialize() - air_contents = new(volume) - START_PROCESSING(SSobj, src) - extended_desc = "Take care not to pressurize it above [round(TANK_FAILURE_PRESSURE)] kPa, or else it will break." - . = ..() - -/obj/item/integrated_circuit/atmospherics/tank/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/atmospherics/tank/do_work() - set_pin_data(IC_OUTPUT, 1, WEAKREF(src)) - push_data() - -/obj/item/integrated_circuit/atmospherics/tank/process() - var/tank_pressure = air_contents.return_pressure() - set_pin_data(IC_OUTPUT, 2, tank_pressure) - push_data() - - //Check if tank broken - if(!broken && tank_pressure > TANK_FAILURE_PRESSURE) - broken = TRUE - to_chat(view(2),"The [name] ruptures, releasing its gases!") - if(broken) - release() - -/obj/item/integrated_circuit/atmospherics/tank/proc/release() - if(air_contents.total_moles() > 0) - playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3) - var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles()) - var/turf/current_turf = get_turf(src) - var/datum/gas_mixture/exterior_gas - if(!current_turf) - return - - exterior_gas = current_turf.return_air() - exterior_gas.merge(expelled_gas) - - -// - large integrated tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank/large - name = "large integrated tank" - desc = "A less small tank for the storage of gases." - volume = 9 - size = 12 - spawn_flags = IC_SPAWN_RESEARCH - - -// - freezer tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank/freezer - name = "freezer tank" - desc = "Cools the gas it contains to a preset temperature." - volume = 6 - size = 8 - inputs = list( - "target temperature" = IC_PINTYPE_NUMBER, - "on" = IC_PINTYPE_BOOLEAN - ) - inputs_default = list("1" = 300) - spawn_flags = IC_SPAWN_RESEARCH - var/temperature = 293.15 - var/heater_coefficient = 0.1 - -/obj/item/integrated_circuit/atmospherics/tank/freezer/on_data_written() - temperature = max(73.15,min(293.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/tank/freezer/process() - var/tank_pressure = air_contents.return_pressure() - set_pin_data(IC_OUTPUT, 2, tank_pressure) - push_data() - - //Cool the tank if the power is on and the temp is above - if(!power_draw_idle || air_contents.temperature < temperature) - return - - air_contents.temperature = max(73.15,air_contents.temperature - (air_contents.temperature - temperature) * heater_coefficient) - - -// - heater tank - // **works** -/obj/item/integrated_circuit/atmospherics/tank/freezer/heater - name = "heater tank" - desc = "Heats the gas it contains to a preset temperature." - volume = 6 - inputs = list( - "target temperature" = IC_PINTYPE_NUMBER, - "on" = IC_PINTYPE_BOOLEAN - ) - spawn_flags = IC_SPAWN_RESEARCH - -/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/on_data_written() - temperature = max(293.15,min(573.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/process() - var/tank_pressure = air_contents.return_pressure() - set_pin_data(IC_OUTPUT, 2, tank_pressure) - push_data() - - //Heat the tank if the power is on or its temperature is below what is set - if(!power_draw_idle || air_contents.temperature > temperature) - return - - air_contents.temperature = min(573.15,air_contents.temperature + (temperature - air_contents.temperature) * heater_coefficient) - - -// - atmospheric cooler - // **works** -/obj/item/integrated_circuit/atmospherics/cooler - name = "atmospheric cooler circuit" - desc = "Cools the air around it." - volume = 6 - size = 13 - spawn_flags = IC_SPAWN_RESEARCH - inputs = list( - "target temperature" = IC_PINTYPE_NUMBER, - "on" = IC_PINTYPE_BOOLEAN - ) - var/temperature = 293.15 - var/heater_coefficient = 0.1 - -/obj/item/integrated_circuit/atmospherics/cooler/Initialize() - air_contents = new(volume) - START_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/atmospherics/cooler/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/atmospherics/cooler/on_data_written() - temperature = max(243.15,min(293.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/cooler/process() - set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) - push_data() - - - //Get the turf you're on and its gas mixture - var/turf/current_turf = get_turf(src) - if(!current_turf) - return - - var/datum/gas_mixture/turf_air = current_turf.return_air() - if(!power_draw_idle || turf_air.temperature < temperature) - return - - //Cool the gas - turf_air.temperature = max(243.15,turf_air.temperature - (turf_air.temperature - temperature) * heater_coefficient) - - -// - atmospheric heater - // **works** -/obj/item/integrated_circuit/atmospherics/cooler/heater - name = "atmospheric heater circuit" - desc = "Heats the air around it." - -/obj/item/integrated_circuit/atmospherics/cooler/heater/on_data_written() - temperature = max(293.15,min(323.15,get_pin_data(IC_INPUT, 1))) - if(get_pin_data(IC_INPUT, 2)) - power_draw_idle = 30 - else - power_draw_idle = 0 - -/obj/item/integrated_circuit/atmospherics/cooler/heater/process() - set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) - push_data() - - //Get the turf and its air mixture - var/turf/current_turf = get_turf(src) - if(!current_turf) - return - - var/datum/gas_mixture/turf_air = current_turf.return_air() - if(!power_draw_idle || turf_air.temperature > temperature) - return - - //Heat the gas - turf_air.temperature = min(323.15,turf_air.temperature + (temperature - turf_air.temperature) * heater_coefficient) - - -// - tank slot - // **works** -/obj/item/integrated_circuit/input/tank_slot - category_text = "Atmospherics" - cooldown_per_use = 1 - name = "tank slot" - desc = "Lets you add a tank to your assembly and remove it even when the assembly is closed." - extended_desc = "It can help you extract gases easier." - complexity = 25 - size = 30 - inputs = list() - outputs = list( - "pressure used" = IC_PINTYPE_NUMBER, - "current tank" = IC_PINTYPE_REF - ) - activators = list( - "push ref" = IC_PINTYPE_PULSE_IN, - "on insert" = IC_PINTYPE_PULSE_OUT, - "on remove" = IC_PINTYPE_PULSE_OUT - ) - spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH - - can_be_asked_input = TRUE - demands_object_input = TRUE - can_input_object_when_closed = TRUE - - var/obj/item/tank/internals/current_tank - -/obj/item/integrated_circuit/input/tank_slot/Initialize() - START_PROCESSING(SSobj, src) - . = ..() - -/obj/item/integrated_circuit/input/tank_slot/process() - push_pressure() - -/obj/item/integrated_circuit/input/tank_slot/attackby(var/obj/item/tank/internals/I, var/mob/living/user) - //Check if it truly is a tank - if(!istype(I,/obj/item/tank/internals)) - to_chat(user,"The [I.name] doesn't seem to fit in here.") - return - - //Check if there is no other tank already inside - if(current_tank) - to_chat(user,"There is already a gas tank inside.") - return - - //The current tank is the one we just attached, its location is inside the circuit - current_tank = I - user.transferItemToLoc(I,src) - to_chat(user,"You put the [I.name] inside the tank slot.") - - //Set the pin to a weak reference of the current tank - push_pressure() - set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) - push_data() - do_work(1) - - -/obj/item/integrated_circuit/input/tank_slot/ask_for_input(mob/user) - attack_self(user) - -/obj/item/integrated_circuit/input/tank_slot/attack_self(mob/user) - //Check if no tank attached - if(!current_tank) - to_chat(user, "There is currently no tank attached.") - return - - //Remove tank and put in user's hands/location - to_chat(user, "You take [current_tank] out of the tank slot.") - user.put_in_hands(current_tank) - current_tank = null - - //Remove tank reference - push_pressure() - set_pin_data(IC_OUTPUT, 2, null) - push_data() - do_work(2) - -/obj/item/integrated_circuit/input/tank_slot/do_work() - set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) - push_data() - -/obj/item/integrated_circuit/input/tank_slot/proc/push_pressure() - if(!current_tank) - set_pin_data(IC_OUTPUT, 1, 0) - return - - var/datum/gas_mixture/tank_air = current_tank.return_air() - if(!tank_air) - set_pin_data(IC_OUTPUT, 1, 0) - return - - set_pin_data(IC_OUTPUT, 1, tank_air.return_pressure()) - push_data() - - -#undef SOURCE_TO_TARGET -#undef TARGET_TO_SOURCE -#undef PUMP_EFFICIENCY -#undef TANK_FAILURE_PRESSURE -#undef PUMP_MAX_PRESSURE -#undef PUMP_MAX_VOLUME +#define SOURCE_TO_TARGET 0 +#define TARGET_TO_SOURCE 1 +#define PUMP_EFFICIENCY 0.6 +#define TANK_FAILURE_PRESSURE (ONE_ATMOSPHERE*25) +#define PUMP_MAX_PRESSURE (ONE_ATMOSPHERE*24) +#define PUMP_MAX_VOLUME 100 + + +/obj/item/integrated_circuit/atmospherics + category_text = "Atmospherics" + cooldown_per_use = 2 SECONDS + complexity = 10 + size = 7 + outputs = list( + "self reference" = IC_PINTYPE_SELFREF, + "pressure" = IC_PINTYPE_NUMBER + ) + var/datum/gas_mixture/air_contents + var/volume = 2 //Pretty small, I know + +/obj/item/integrated_circuit/atmospherics/Initialize() + air_contents = new(volume) + ..() + +/obj/item/integrated_circuit/atmospherics/return_air() + return air_contents + +//Check if the gas container is adjacent and of the right type +/obj/item/integrated_circuit/atmospherics/proc/check_gassource(atom/gasholder) + if(!gasholder) + return FALSE + if(!gasholder.Adjacent(get_object())) + return FALSE + if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) + return FALSE + return TRUE + +//Needed in circuits where source and target types differ +/obj/item/integrated_circuit/atmospherics/proc/check_gastarget(atom/gasholder) + return check_gassource(gasholder) + + +// - gas pump - // **works** +/obj/item/integrated_circuit/atmospherics/pump + name = "gas pump" + desc = "Somehow moves gases between two tanks, canisters, and other gas containers." + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( + "source" = IC_PINTYPE_REF, + "target" = IC_PINTYPE_REF, + "target pressure" = IC_PINTYPE_NUMBER + ) + activators = list( + "transfer" = IC_PINTYPE_PULSE_IN, + "on transfer" = IC_PINTYPE_PULSE_OUT + ) + var/direction = SOURCE_TO_TARGET + var/target_pressure = PUMP_MAX_PRESSURE + power_draw_per_use = 20 + +/obj/item/integrated_circuit/atmospherics/pump/Initialize() + air_contents = new(volume) + extended_desc += " Use negative pressure to move air from target to source. \ + Note that only part of the gas is moved on each transfer, \ + so multiple activations will be necessary to achieve target pressure. \ + The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." + . = ..() + +// This proc gets the direction of the gas flow depending on its value, by calling update target +/obj/item/integrated_circuit/atmospherics/pump/on_data_written() + var/amt = get_pin_data(IC_INPUT, 3) + update_target(amt) + +/obj/item/integrated_circuit/atmospherics/pump/proc/update_target(new_amount) + if(!isnum(new_amount)) + new_amount = 0 + // See in which direction the gas moves + if(new_amount < 0) + direction = TARGET_TO_SOURCE + else + direction = SOURCE_TO_TARGET + target_pressure = min(round(PUMP_MAX_PRESSURE),abs(new_amount)) + +/obj/item/integrated_circuit/atmospherics/pump/do_work() + var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) + var/obj/target = get_pin_data_as_type(IC_INPUT, 2, /obj) + perform_magic(source, target) + activate_pin(2) + +/obj/item/integrated_circuit/atmospherics/pump/proc/perform_magic(atom/source, atom/target) + //Check if both atoms are of the right type: atmos circuits/gas tanks/canisters. If one is the same, use the circuit var + if(!check_gassource(source)) + source = src + + if(!check_gastarget(target)) + target = src + + // If both are the same, this whole proc would do nothing and just waste performance + if(source == target) + return + + var/datum/gas_mixture/source_air = source.return_air() + var/datum/gas_mixture/target_air = target.return_air() + + if(!source_air || !target_air) + return + + // Swapping both source and target + if(direction == TARGET_TO_SOURCE) + var/temp = source_air + source_air = target_air + target_air = temp + + // If what you are pumping is empty, use the circuit's storage + if(source_air.total_moles() <= 0) + source_air = air_contents + + // Move gas from one place to another + move_gas(source_air, target_air) + air_update_turf() + +/obj/item/integrated_circuit/atmospherics/pump/proc/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) + + // No moles = nothing to pump + if(source_air.total_moles() <= 0 || target_air.return_pressure() >= PUMP_MAX_PRESSURE) + return + + // Negative Kelvin temperatures should never happen and if they do, normalize them + if(source_air.temperature < TCMB) + source_air.temperature = TCMB + + var/pressure_delta = target_pressure - target_air.return_pressure() + if(pressure_delta > 0.1) + var/transfer_moles = (pressure_delta*target_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY + var/datum/gas_mixture/removed = source_air.remove(transfer_moles) + target_air.merge(removed) + + +// - volume pump - // **Works** +/obj/item/integrated_circuit/atmospherics/pump/volume + name = "volume pump" + desc = "Moves gases between two tanks, canisters, and other gas containers by using their volume, up to 200 L/s." + extended_desc = " Use negative volume to move air from target to source. Note that only part of the gas is moved on each transfer. Its maximum pumping volume is capped at 1000kPa." + + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( + "source" = IC_PINTYPE_REF, + "target" = IC_PINTYPE_REF, + "transfer volume" = IC_PINTYPE_NUMBER + ) + activators = list( + "transfer" = IC_PINTYPE_PULSE_IN, + "on transfer" = IC_PINTYPE_PULSE_OUT + ) + direction = SOURCE_TO_TARGET + var/transfer_rate = PUMP_MAX_VOLUME + power_draw_per_use = 20 + +/obj/item/integrated_circuit/atmospherics/pump/volume/update_target(new_amount) + if(!isnum(new_amount)) + new_amount = 0 + // See in which direction the gas moves + if(new_amount < 0) + direction = TARGET_TO_SOURCE + else + direction = SOURCE_TO_TARGET + target_pressure = min(PUMP_MAX_VOLUME,abs(new_amount)) + +/obj/item/integrated_circuit/atmospherics/pump/volume/move_gas(datum/gas_mixture/source_air, datum/gas_mixture/target_air) + // No moles = nothing to pump + if(source_air.total_moles() <= 0) + return + + // Negative Kelvin temperatures should never happen and if they do, normalize them + if(source_air.temperature < TCMB) + source_air.temperature = TCMB + + if((source_air.return_pressure() < 0.01) || (target_air.return_pressure() >= PUMP_MAX_PRESSURE)) + return + + //The second part of the min caps the pressure built by the volume pumps to the max pump pressure + var/transfer_ratio = min(transfer_rate,target_air.volume*PUMP_MAX_PRESSURE/source_air.return_pressure())/source_air.volume + + var/datum/gas_mixture/removed = source_air.remove_ratio(transfer_ratio * PUMP_EFFICIENCY) + + target_air.merge(removed) + + +// - gas vent - // **works** +/obj/item/integrated_circuit/atmospherics/pump/vent + name = "gas vent" + extended_desc = "Use negative volume to move air from target to environment. Note that only part of the gas is moved on each transfer. Unlike the gas pump, this one keeps pumping even further to pressures of 9000 pKa and it is not advised to use it on tank circuits." + desc = "Moves gases between the environment and adjacent gas containers." + inputs = list( + "container" = IC_PINTYPE_REF, + "target pressure" = IC_PINTYPE_NUMBER + ) + +/obj/item/integrated_circuit/atmospherics/pump/vent/on_data_written() + var/amt = get_pin_data(IC_INPUT, 2) + update_target(amt) + +/obj/item/integrated_circuit/atmospherics/pump/vent/do_work() + var/turf/source = get_turf(src) + var/obj/target = get_pin_data_as_type(IC_INPUT, 1, /obj) + perform_magic(source, target) + activate_pin(2) + +/obj/item/integrated_circuit/atmospherics/pump/vent/check_gastarget(atom/gasholder) + if(!gasholder) + return FALSE + if(!gasholder.Adjacent(get_object())) + return FALSE + if(!istype(gasholder, /obj/item/tank) && !istype(gasholder, /obj/machinery/portable_atmospherics) && !istype(gasholder, /obj/item/integrated_circuit/atmospherics)) + return FALSE + return TRUE + + +/obj/item/integrated_circuit/atmospherics/pump/vent/check_gassource(atom/target) + if(!target) + return FALSE + if(!istype(target, /turf)) + return FALSE + return TRUE + + +// - integrated connector - // Can connect and disconnect properly +/obj/item/integrated_circuit/atmospherics/connector + name = "integrated connector" + desc = "Creates an airtight seal with standard connectors found on the floor, \ + allowing the assembly to exchange gases with a pipe network." + extended_desc = "This circuit will automatically attempt to locate and connect to ports on the floor beneath it when activated. \ + You must set a target before connecting." + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + inputs = list( + "target" = IC_PINTYPE_REF + ) + activators = list( + "toggle connection" = IC_PINTYPE_PULSE_IN, + "on connected" = IC_PINTYPE_PULSE_OUT, + "on connection failed" = IC_PINTYPE_PULSE_OUT, + "on disconnected" = IC_PINTYPE_PULSE_OUT + ) + + var/obj/machinery/atmospherics/components/unary/portables_connector/connector + +/obj/item/integrated_circuit/atmospherics/connector/Initialize() + air_contents = new(volume) + START_PROCESSING(SSobj, src) + . = ..() + +//Sucks up the gas from the connector +/obj/item/integrated_circuit/atmospherics/connector/process() + set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) + +/obj/item/integrated_circuit/atmospherics/connector/check_gassource(atom/gasholder) + if(!gasholder) + return FALSE + if(!istype(gasholder,/obj/machinery/atmospherics/components/unary/portables_connector)) + return FALSE + return TRUE + +//If the assembly containing this is moved from the tile the connector pipe is in, the connection breaks +/obj/item/integrated_circuit/atmospherics/connector/ext_moved() + if(connector) + if(get_dist(get_object(), connector) > 0) + // The assembly is set as connected device and the connector handles the rest + connector.connected_device = null + connector = null + activate_pin(4) + +/obj/item/integrated_circuit/atmospherics/connector/do_work() + // If there is a connection, disconnect + if(connector) + connector.connected_device = null + connector = null + activate_pin(4) + return + + var/obj/machinery/atmospherics/components/unary/portables_connector/PC = locate() in get_turf(src) + // If no connector can't connect + if(!PC) + activate_pin(3) + return + connector = PC + connector.connected_device = src + activate_pin(2) + +// Required for making the connector port script work +obj/item/integrated_circuit/atmospherics/connector/portableConnectorReturnAir() + return air_contents + + +// - gas filter - // **works** +/obj/item/integrated_circuit/atmospherics/pump/filter + name = "gas filter" + desc = "Filters one gas out of a mixture." + complexity = 20 + size = 8 + spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "source" = IC_PINTYPE_REF, + "filtered output" = IC_PINTYPE_REF, + "contaminants output" = IC_PINTYPE_REF, + "wanted gases" = IC_PINTYPE_LIST, + "target pressure" = IC_PINTYPE_NUMBER + ) + power_draw_per_use = 30 + +/obj/item/integrated_circuit/atmospherics/pump/filter/on_data_written() + var/amt = get_pin_data(IC_INPUT, 5) + target_pressure = CLAMP(amt, 0, PUMP_MAX_PRESSURE) + +/obj/item/integrated_circuit/atmospherics/pump/filter/do_work() + activate_pin(2) + var/obj/source = get_pin_data_as_type(IC_INPUT, 1, /obj) + var/obj/filtered = get_pin_data_as_type(IC_INPUT, 2, /obj) + var/obj/contaminants = get_pin_data_as_type(IC_INPUT, 3, /obj) + + var/wanted = get_pin_data(IC_INPUT, 4) + + // If there is no filtered output, this whole thing makes no sense + if(!check_gassource(filtered)) + return + + var/datum/gas_mixture/filtered_air = filtered.return_air() + if(!filtered_air) + return + + // If no source is set, the source is possibly this circuit's content + if(!check_gassource(source)) + source = src + var/datum/gas_mixture/source_air = source.return_air() + + //No source air: source is this circuit + if(!source_air) + source_air = air_contents + + // If no filtering tank is set, filter through itself + if(!check_gassource(contaminants)) + contaminants = src + var/datum/gas_mixture/contaminated_air = contaminants.return_air() + + //If there is no gas mixture datum for unfiltered, pump the contaminants back into the circuit + if(!contaminated_air) + contaminated_air = air_contents + + if(contaminated_air.return_pressure() >= PUMP_MAX_PRESSURE || filtered_air.return_pressure() >= PUMP_MAX_PRESSURE) + return + + var/pressure_delta = target_pressure - contaminated_air.return_pressure() + var/transfer_moles + + //Negative Kelvins are an anomaly and should be normalized if encountered + if(source_air.temperature < TCMB) + source_air.temperature = TCMB + + transfer_moles = (pressure_delta*contaminated_air.volume/(source_air.temperature * R_IDEAL_GAS_EQUATION))*PUMP_EFFICIENCY + + //If there is nothing to transfer, just return + if(transfer_moles <= 0) + return + + //This is the var that holds the currently filtered part of the gas + var/datum/gas_mixture/removed = source_air.remove(transfer_moles) + if(!removed) + return + + //This is the gas that will be moved from source to filtered + var/datum/gas_mixture/filtered_out = new + + for(var/filtered_gas in removed.gases) + //Get the name of the gas and see if it is in the list + if(GLOB.meta_gas_names[filtered_gas] in wanted) + //The gas that is put in all the filtered out gases + filtered_out.temperature = removed.temperature + filtered_out.gases[filtered_gas] = removed.gases[filtered_gas] + + //The filtered out gas is entirely removed from the currently filtered gases + removed.gases[filtered_gas] = 0 + GAS_GARBAGE_COLLECT(removed.gases) + + //Check if the pressure is high enough to put stuff in filtered, or else just put it back in the source + var/datum/gas_mixture/target = (filtered_air.return_pressure() < target_pressure ? filtered_air : source_air) + target.merge(filtered_out) + contaminated_air.merge(removed) + + +/obj/item/integrated_circuit/atmospherics/pump/filter/Initialize() + air_contents = new(volume) + . = ..() + extended_desc = "Remember to properly spell and capitalize the filtered gas name. \ + Note that only part of the gas is moved on each transfer, \ + so multiple activations will be necessary to achieve target pressure. \ + The pressure limit for circuit pumps is [round(PUMP_MAX_PRESSURE)] kPa." + + +// - gas mixer - // **works** +/obj/item/integrated_circuit/atmospherics/pump/mixer + name = "gas mixer" + desc = "Mixes 2 different types of gases." + complexity = 20 + size = 8 + spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "first source" = IC_PINTYPE_REF, + "second source" = IC_PINTYPE_REF, + "output" = IC_PINTYPE_REF, + "first source percentage" = IC_PINTYPE_NUMBER, + "target pressure" = IC_PINTYPE_NUMBER + ) + power_draw_per_use = 30 + +/obj/item/integrated_circuit/atmospherics/pump/mixer/do_work() + activate_pin(2) + var/obj/source_1 = get_pin_data(IC_INPUT, 1) + var/obj/source_2 = get_pin_data(IC_INPUT, 2) + var/obj/gas_output = get_pin_data(IC_INPUT, 3) + if(!check_gassource(source_1)) + source_1 = src + + if(!check_gassource(source_2)) + source_2 = src + + if(!check_gassource(gas_output)) + gas_output = src + + if(source_1 == gas_output || source_2 == gas_output) + return + + var/datum/gas_mixture/source_1_gases = source_1.return_air() + var/datum/gas_mixture/source_2_gases = source_2.return_air() + var/datum/gas_mixture/output_gases = gas_output.return_air() + + if(!source_1_gases || !source_2_gases || !output_gases) + return + + if(output_gases.return_pressure() >= PUMP_MAX_PRESSURE) + return + + if(source_1_gases.return_pressure() <= 0 || source_2_gases.return_pressure() <= 0) + return + + //This calculates how much should be sent + var/gas_percentage = round(max(min(get_pin_data(IC_INPUT, 4),100),0) / 100) + + //Basically: number of moles = percentage of pressure filled up * efficiency coefficient * (pressure from both gases * volume of output) / (R * Temperature) + var/transfer_moles = (get_pin_data(IC_INPUT, 5) / max(1,output_gases.return_pressure())) * PUMP_EFFICIENCY * (source_1_gases.return_pressure() * gas_percentage + source_2_gases.return_pressure() * (1 - gas_percentage)) * output_gases.volume/ (R_IDEAL_GAS_EQUATION * max(output_gases.temperature,TCMB)) + + + if(transfer_moles <= 0) + return + + var/datum/gas_mixture/mix = source_1_gases.remove(transfer_moles * gas_percentage) + output_gases.merge(mix) + mix = source_2_gases.remove(transfer_moles * (1-gas_percentage)) + output_gases.merge(mix) + + +// - integrated tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank + name = "integrated tank" + desc = "A small tank for the storage of gases." + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + size = 4 + activators = list( + "push ref" = IC_PINTYPE_PULSE_IN + ) + volume = 3 //emergency tank sized + var/broken = FALSE + +/obj/item/integrated_circuit/atmospherics/tank/Initialize() + air_contents = new(volume) + START_PROCESSING(SSobj, src) + extended_desc = "Take care not to pressurize it above [round(TANK_FAILURE_PRESSURE)] kPa, or else it will break." + . = ..() + +/obj/item/integrated_circuit/atmospherics/tank/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/atmospherics/tank/do_work() + set_pin_data(IC_OUTPUT, 1, WEAKREF(src)) + push_data() + +/obj/item/integrated_circuit/atmospherics/tank/process() + var/tank_pressure = air_contents.return_pressure() + set_pin_data(IC_OUTPUT, 2, tank_pressure) + push_data() + + //Check if tank broken + if(!broken && tank_pressure > TANK_FAILURE_PRESSURE) + broken = TRUE + to_chat(view(2),"The [name] ruptures, releasing its gases!") + if(broken) + release() + +/obj/item/integrated_circuit/atmospherics/tank/proc/release() + if(air_contents.total_moles() > 0) + playsound(loc, 'sound/effects/spray.ogg', 10, 1, -3) + var/datum/gas_mixture/expelled_gas = air_contents.remove(air_contents.total_moles()) + var/turf/current_turf = get_turf(src) + var/datum/gas_mixture/exterior_gas + if(!current_turf) + return + + exterior_gas = current_turf.return_air() + exterior_gas.merge(expelled_gas) + + +// - large integrated tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank/large + name = "large integrated tank" + desc = "A less small tank for the storage of gases." + volume = 9 + size = 12 + spawn_flags = IC_SPAWN_RESEARCH + + +// - freezer tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank/freezer + name = "freezer tank" + desc = "Cools the gas it contains to a preset temperature." + volume = 6 + size = 8 + inputs = list( + "target temperature" = IC_PINTYPE_NUMBER, + "on" = IC_PINTYPE_BOOLEAN + ) + inputs_default = list("1" = 300) + spawn_flags = IC_SPAWN_RESEARCH + var/temperature = 293.15 + var/heater_coefficient = 0.1 + +/obj/item/integrated_circuit/atmospherics/tank/freezer/on_data_written() + temperature = max(73.15,min(293.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/tank/freezer/process() + var/tank_pressure = air_contents.return_pressure() + set_pin_data(IC_OUTPUT, 2, tank_pressure) + push_data() + + //Cool the tank if the power is on and the temp is above + if(!power_draw_idle || air_contents.temperature < temperature) + return + + air_contents.temperature = max(73.15,air_contents.temperature - (air_contents.temperature - temperature) * heater_coefficient) + + +// - heater tank - // **works** +/obj/item/integrated_circuit/atmospherics/tank/freezer/heater + name = "heater tank" + desc = "Heats the gas it contains to a preset temperature." + volume = 6 + inputs = list( + "target temperature" = IC_PINTYPE_NUMBER, + "on" = IC_PINTYPE_BOOLEAN + ) + spawn_flags = IC_SPAWN_RESEARCH + +/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/on_data_written() + temperature = max(293.15,min(573.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/tank/freezer/heater/process() + var/tank_pressure = air_contents.return_pressure() + set_pin_data(IC_OUTPUT, 2, tank_pressure) + push_data() + + //Heat the tank if the power is on or its temperature is below what is set + if(!power_draw_idle || air_contents.temperature > temperature) + return + + air_contents.temperature = min(573.15,air_contents.temperature + (temperature - air_contents.temperature) * heater_coefficient) + + +// - atmospheric cooler - // **works** +/obj/item/integrated_circuit/atmospherics/cooler + name = "atmospheric cooler circuit" + desc = "Cools the air around it." + volume = 6 + size = 13 + spawn_flags = IC_SPAWN_RESEARCH + inputs = list( + "target temperature" = IC_PINTYPE_NUMBER, + "on" = IC_PINTYPE_BOOLEAN + ) + var/temperature = 293.15 + var/heater_coefficient = 0.1 + +/obj/item/integrated_circuit/atmospherics/cooler/Initialize() + air_contents = new(volume) + START_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/atmospherics/cooler/Destroy() + STOP_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/atmospherics/cooler/on_data_written() + temperature = max(243.15,min(293.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/cooler/process() + set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) + push_data() + + + //Get the turf you're on and its gas mixture + var/turf/current_turf = get_turf(src) + if(!current_turf) + return + + var/datum/gas_mixture/turf_air = current_turf.return_air() + if(!power_draw_idle || turf_air.temperature < temperature) + return + + //Cool the gas + turf_air.temperature = max(243.15,turf_air.temperature - (turf_air.temperature - temperature) * heater_coefficient) + + +// - atmospheric heater - // **works** +/obj/item/integrated_circuit/atmospherics/cooler/heater + name = "atmospheric heater circuit" + desc = "Heats the air around it." + +/obj/item/integrated_circuit/atmospherics/cooler/heater/on_data_written() + temperature = max(293.15,min(323.15,get_pin_data(IC_INPUT, 1))) + if(get_pin_data(IC_INPUT, 2)) + power_draw_idle = 30 + else + power_draw_idle = 0 + +/obj/item/integrated_circuit/atmospherics/cooler/heater/process() + set_pin_data(IC_OUTPUT, 2, air_contents.return_pressure()) + push_data() + + //Get the turf and its air mixture + var/turf/current_turf = get_turf(src) + if(!current_turf) + return + + var/datum/gas_mixture/turf_air = current_turf.return_air() + if(!power_draw_idle || turf_air.temperature > temperature) + return + + //Heat the gas + turf_air.temperature = min(323.15,turf_air.temperature + (temperature - turf_air.temperature) * heater_coefficient) + + +// - tank slot - // **works** +/obj/item/integrated_circuit/input/tank_slot + category_text = "Atmospherics" + cooldown_per_use = 1 + name = "tank slot" + desc = "Lets you add a tank to your assembly and remove it even when the assembly is closed." + extended_desc = "It can help you extract gases easier." + complexity = 25 + size = 30 + inputs = list() + outputs = list( + "pressure used" = IC_PINTYPE_NUMBER, + "current tank" = IC_PINTYPE_REF + ) + activators = list( + "push ref" = IC_PINTYPE_PULSE_IN, + "on insert" = IC_PINTYPE_PULSE_OUT, + "on remove" = IC_PINTYPE_PULSE_OUT + ) + spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH + + can_be_asked_input = TRUE + demands_object_input = TRUE + can_input_object_when_closed = TRUE + + var/obj/item/tank/internals/current_tank + +/obj/item/integrated_circuit/input/tank_slot/Initialize() + START_PROCESSING(SSobj, src) + . = ..() + +/obj/item/integrated_circuit/input/tank_slot/process() + push_pressure() + +/obj/item/integrated_circuit/input/tank_slot/attackby(var/obj/item/tank/internals/I, var/mob/living/user) + //Check if it truly is a tank + if(!istype(I,/obj/item/tank/internals)) + to_chat(user,"The [I.name] doesn't seem to fit in here.") + return + + //Check if there is no other tank already inside + if(current_tank) + to_chat(user,"There is already a gas tank inside.") + return + + //The current tank is the one we just attached, its location is inside the circuit + current_tank = I + user.transferItemToLoc(I,src) + to_chat(user,"You put the [I.name] inside the tank slot.") + + //Set the pin to a weak reference of the current tank + push_pressure() + set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) + push_data() + do_work(1) + + +/obj/item/integrated_circuit/input/tank_slot/ask_for_input(mob/user) + attack_self(user) + +/obj/item/integrated_circuit/input/tank_slot/attack_self(mob/user) + //Check if no tank attached + if(!current_tank) + to_chat(user, "There is currently no tank attached.") + return + + //Remove tank and put in user's hands/location + to_chat(user, "You take [current_tank] out of the tank slot.") + user.put_in_hands(current_tank) + current_tank = null + + //Remove tank reference + push_pressure() + set_pin_data(IC_OUTPUT, 2, null) + push_data() + do_work(2) + +/obj/item/integrated_circuit/input/tank_slot/do_work() + set_pin_data(IC_OUTPUT, 2, WEAKREF(current_tank)) + push_data() + +/obj/item/integrated_circuit/input/tank_slot/proc/push_pressure() + if(!current_tank) + set_pin_data(IC_OUTPUT, 1, 0) + return + + var/datum/gas_mixture/tank_air = current_tank.return_air() + if(!tank_air) + set_pin_data(IC_OUTPUT, 1, 0) + return + + set_pin_data(IC_OUTPUT, 1, tank_air.return_pressure()) + push_data() + + +#undef SOURCE_TO_TARGET +#undef TARGET_TO_SOURCE +#undef PUMP_EFFICIENCY +#undef TANK_FAILURE_PRESSURE +#undef PUMP_MAX_PRESSURE +#undef PUMP_MAX_VOLUME diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm index a7c58e8669..b258a15972 100644 --- a/code/modules/integrated_electronics/subtypes/input.dm +++ b/code/modules/integrated_electronics/subtypes/input.dm @@ -1165,8 +1165,8 @@ var/list/gas_names = list() var/list/gas_amounts = list() for(var/id in gases) - var/name = gases[id][GAS_META][META_GAS_NAME] - var/amt = round(gases[id][MOLES], 0.001) + var/name = GLOB.meta_gas_names[id] + var/amt = round(gases[id], 0.001) gas_names.Add(name) gas_amounts.Add(amt) diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index 543fda9fd0..b8edd34ee9 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -15,25 +15,23 @@ var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME var/list/breath_gases = breath.gases - breath.assert_gases(/datum/gas/plasma, /datum/gas/oxygen) - //Partial pressure of the toxins in our breath - var/Toxins_pp = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure + var/Toxins_pp = (breath_gases[/datum/gas/plasma]/breath.total_moles())*breath_pressure if(Toxins_pp > tox_detect_threshold) // Detect toxins in air - adjustPlasma(breath_gases[/datum/gas/plasma][MOLES]*250) + adjustPlasma(breath_gases[/datum/gas/plasma]*250) throw_alert("alien_tox", /obj/screen/alert/alien_tox) - toxins_used = breath_gases[/datum/gas/plasma][MOLES] + toxins_used = breath_gases[/datum/gas/plasma] else clear_alert("alien_tox") //Breathe in toxins and out oxygen - breath_gases[/datum/gas/plasma][MOLES] -= toxins_used - breath_gases[/datum/gas/oxygen][MOLES] += toxins_used + breath_gases[/datum/gas/plasma] -= toxins_used + breath_gases[/datum/gas/oxygen] += toxins_used - breath.garbage_collect() + GAS_GARBAGE_COLLECT(breath.gases) //BREATH TEMPERATURE handle_breath_temperature(breath) diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 3095ca48ae..afd6e4e7f5 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -33,7 +33,7 @@ if((!istype(H.w_uniform, /obj/item/clothing/under/plasmaman) || !istype(H.head, /obj/item/clothing/head/helmet/space/plasmaman)) && !atmos_sealed) if(environment) if(environment.total_moles()) - if(environment.gases[/datum/gas/oxygen] && (environment.gases[/datum/gas/oxygen][MOLES]) >= 1) //Same threshhold that extinguishes fire + if(environment.gases[/datum/gas/oxygen] && (environment.gases[/datum/gas/oxygen]) >= 1) //Same threshhold that extinguishes fire H.adjust_fire_stacks(0.5) if(!H.on_fire && H.fire_stacks > 0) H.visible_message("[H]'s body reacts with the atmosphere and bursts into flames!","Your body reacts with the atmosphere and bursts into flame!") diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index e757e6dcf4..9c0eca4e8a 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -150,10 +150,9 @@ var/breath_pressure = (breath.total_moles()*R_IDEAL_GAS_EQUATION*breath.temperature)/BREATH_VOLUME var/list/breath_gases = breath.gases - breath.assert_gases(/datum/gas/oxygen, /datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz) - var/O2_partialpressure = (breath_gases[/datum/gas/oxygen][MOLES]/breath.total_moles())*breath_pressure - var/Toxins_partialpressure = (breath_gases[/datum/gas/plasma][MOLES]/breath.total_moles())*breath_pressure - var/CO2_partialpressure = (breath_gases[/datum/gas/carbon_dioxide][MOLES]/breath.total_moles())*breath_pressure + var/O2_partialpressure = (breath_gases[/datum/gas/oxygen]/breath.total_moles())*breath_pressure + var/Toxins_partialpressure = (breath_gases[/datum/gas/plasma]/breath.total_moles())*breath_pressure + var/CO2_partialpressure = (breath_gases[/datum/gas/carbon_dioxide]/breath.total_moles())*breath_pressure //OXYGEN @@ -177,7 +176,7 @@ var/ratio = 1 - O2_partialpressure/safe_oxy_min adjustOxyLoss(min(5*ratio, 3)) failed_last_breath = 1 - oxygen_used = breath_gases[/datum/gas/oxygen][MOLES]*ratio + oxygen_used = breath_gases[/datum/gas/oxygen]*ratio else adjustOxyLoss(3) failed_last_breath = 1 @@ -189,12 +188,12 @@ o2overloadtime = 0 //reset our counter for this too if(health >= crit_threshold) adjustOxyLoss(-5) - oxygen_used = breath_gases[/datum/gas/oxygen][MOLES] + oxygen_used = breath_gases[/datum/gas/oxygen] clear_alert("not_enough_oxy") SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "suffocation") - breath_gases[/datum/gas/oxygen][MOLES] -= oxygen_used - breath_gases[/datum/gas/carbon_dioxide][MOLES] += oxygen_used + breath_gases[/datum/gas/oxygen] -= oxygen_used + breath_gases[/datum/gas/carbon_dioxide] += oxygen_used //CARBON DIOXIDE if(CO2_partialpressure > safe_co2_max) @@ -213,7 +212,7 @@ //TOXINS/PLASMA if(Toxins_partialpressure > safe_tox_max) - var/ratio = (breath_gases[/datum/gas/plasma][MOLES]/safe_tox_max) * 10 + var/ratio = (breath_gases[/datum/gas/plasma]/safe_tox_max) * 10 adjustToxLoss(CLAMP(ratio, MIN_TOXIC_GAS_DAMAGE, MAX_TOXIC_GAS_DAMAGE)) throw_alert("too_much_tox", /obj/screen/alert/too_much_tox) else @@ -221,7 +220,7 @@ //NITROUS OXIDE if(breath_gases[/datum/gas/nitrous_oxide]) - var/SA_partialpressure = (breath_gases[/datum/gas/nitrous_oxide][MOLES]/breath.total_moles())*breath_pressure + var/SA_partialpressure = (breath_gases[/datum/gas/nitrous_oxide]/breath.total_moles())*breath_pressure if(SA_partialpressure > SA_para_min) Unconscious(60) if(SA_partialpressure > SA_sleep_min) @@ -232,7 +231,7 @@ //BZ (Facepunch port of their Agent B) if(breath_gases[/datum/gas/bz]) - var/bz_partialpressure = (breath_gases[/datum/gas/bz][MOLES]/breath.total_moles())*breath_pressure + var/bz_partialpressure = (breath_gases[/datum/gas/bz]/breath.total_moles())*breath_pressure if(bz_partialpressure > 1) hallucination += 10 else if(bz_partialpressure > 0.01) @@ -240,17 +239,17 @@ //TRITIUM if(breath_gases[/datum/gas/tritium]) - var/tritium_partialpressure = (breath_gases[/datum/gas/tritium][MOLES]/breath.total_moles())*breath_pressure + var/tritium_partialpressure = (breath_gases[/datum/gas/tritium]/breath.total_moles())*breath_pressure radiation += tritium_partialpressure/10 //NITRYL if(breath_gases[/datum/gas/nitryl]) - var/nitryl_partialpressure = (breath_gases[/datum/gas/nitryl][MOLES]/breath.total_moles())*breath_pressure + var/nitryl_partialpressure = (breath_gases[/datum/gas/nitryl]/breath.total_moles())*breath_pressure adjustFireLoss(nitryl_partialpressure/4) //MIASMA if(breath_gases[/datum/gas/miasma]) - var/miasma_partialpressure = (breath_gases[/datum/gas/miasma][MOLES]/breath.total_moles())*breath_pressure + var/miasma_partialpressure = (breath_gases[/datum/gas/miasma]/breath.total_moles())*breath_pressure if(prob(1 * miasma_partialpressure)) var/datum/disease/advance/miasma_disease = new /datum/disease/advance/random(2,3) @@ -292,7 +291,7 @@ - breath.garbage_collect() + GAS_GARBAGE_COLLECT(breath.gases) //BREATH TEMPERATURE handle_breath_temperature(breath) @@ -345,8 +344,7 @@ var/list/cached_gases = miasma_turf.air.gases - ASSERT_GAS(/datum/gas/miasma, miasma_turf.air) - cached_gases[/datum/gas/miasma][MOLES] += 0.02 + cached_gases[/datum/gas/miasma] += 0.02 /mob/living/carbon/proc/handle_blood() return diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 9ef4c1567a..cb7b9a469c 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -109,7 +109,7 @@ ExtinguishMob() return var/datum/gas_mixture/G = loc.return_air() // Check if we're standing in an oxygenless environment - if(!G.gases[/datum/gas/oxygen] || G.gases[/datum/gas/oxygen][MOLES] < 1) + if(G.gases[/datum/gas/oxygen] < 1) ExtinguishMob() //If there's no oxygen in the tile we're on, put out the fire return var/turf/location = get_turf(src) diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 90070f1d5d..0e8d37a807 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -544,9 +544,9 @@ if (total_moles) for(var/id in env_gases) - var/gas_level = env_gases[id][MOLES]/total_moles + var/gas_level = env_gases[id]/total_moles if(gas_level > 0.01) - dat += "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_level*100)]%
" + dat += "[GLOB.meta_gas_names[id]]: [round(gas_level*100)]%
" dat += "Temperature: [round(environment.temperature-T0C)]°C
" dat += "Refresh Reading
" dat += "
" diff --git a/code/modules/mob/living/simple_animal/hostile/tree.dm b/code/modules/mob/living/simple_animal/hostile/tree.dm index 3d10bfd121..fc51b9afe4 100644 --- a/code/modules/mob/living/simple_animal/hostile/tree.dm +++ b/code/modules/mob/living/simple_animal/hostile/tree.dm @@ -1,71 +1,71 @@ -/mob/living/simple_animal/hostile/tree - name = "pine tree" - desc = "A pissed off tree-like alien. It seems annoyed with the festivities..." - icon = 'icons/obj/flora/pinetrees.dmi' - icon_state = "pine_1" - icon_living = "pine_1" - icon_dead = "pine_1" - icon_gib = "pine_1" - gender = NEUTER - speak_chance = 0 - turns_per_move = 5 - response_help = "brushes" - response_disarm = "pushes" - response_harm = "hits" - speed = 1 - maxHealth = 250 - health = 250 - mob_size = MOB_SIZE_LARGE - - pixel_x = -16 - - harm_intent_damage = 5 - melee_damage_lower = 8 - melee_damage_upper = 12 - attacktext = "bites" - attack_sound = 'sound/weapons/bite.ogg' - speak_emote = list("pines") - emote_taunt = list("growls") - taunt_chance = 20 - - atmos_requirements = list("min_oxy" = 2, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) - unsuitable_atmos_damage = 5 - minbodytemp = 0 - maxbodytemp = 1200 - - faction = list("hostile") - deathmessage = "is hacked into pieces!" - loot = list(/obj/item/stack/sheet/mineral/wood) - gold_core_spawnable = HOSTILE_SPAWN - del_on_death = 1 - -/mob/living/simple_animal/hostile/tree/Life() - ..() - if(isopenturf(loc)) - var/turf/open/T = src.loc - if(T.air && T.air.gases[/datum/gas/carbon_dioxide]) - var/co2 = T.air.gases[/datum/gas/carbon_dioxide][MOLES] - if(co2 > 0) - if(prob(25)) - var/amt = min(co2, 9) - T.air.gases[/datum/gas/carbon_dioxide][MOLES] -= amt - T.atmos_spawn_air("o2=[amt]") - -/mob/living/simple_animal/hostile/tree/AttackingTarget() - . = ..() - if(iscarbon(target)) - var/mob/living/carbon/C = target - if(prob(15)) - C.Knockdown(60) - C.visible_message("\The [src] knocks down \the [C]!", \ - "\The [src] knocks you down!") - -/mob/living/simple_animal/hostile/tree/festivus - name = "festivus pole" - desc = "Serenity now... SERENITY NOW!" - icon_state = "festivus_pole" - icon_living = "festivus_pole" - icon_dead = "festivus_pole" - icon_gib = "festivus_pole" - loot = list(/obj/item/stack/rods) - speak_emote = list("polls") +/mob/living/simple_animal/hostile/tree + name = "pine tree" + desc = "A pissed off tree-like alien. It seems annoyed with the festivities..." + icon = 'icons/obj/flora/pinetrees.dmi' + icon_state = "pine_1" + icon_living = "pine_1" + icon_dead = "pine_1" + icon_gib = "pine_1" + gender = NEUTER + speak_chance = 0 + turns_per_move = 5 + response_help = "brushes" + response_disarm = "pushes" + response_harm = "hits" + speed = 1 + maxHealth = 250 + health = 250 + mob_size = MOB_SIZE_LARGE + + pixel_x = -16 + + harm_intent_damage = 5 + melee_damage_lower = 8 + melee_damage_upper = 12 + attacktext = "bites" + attack_sound = 'sound/weapons/bite.ogg' + speak_emote = list("pines") + emote_taunt = list("growls") + taunt_chance = 20 + + atmos_requirements = list("min_oxy" = 2, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + unsuitable_atmos_damage = 5 + minbodytemp = 0 + maxbodytemp = 1200 + + faction = list("hostile") + deathmessage = "is hacked into pieces!" + loot = list(/obj/item/stack/sheet/mineral/wood) + gold_core_spawnable = HOSTILE_SPAWN + del_on_death = 1 + +/mob/living/simple_animal/hostile/tree/Life() + ..() + if(isopenturf(loc)) + var/turf/open/T = src.loc + if(T.air && T.air.gases[/datum/gas/carbon_dioxide]) + var/co2 = T.air.gases[/datum/gas/carbon_dioxide] + if(co2 > 0) + if(prob(25)) + var/amt = min(co2, 9) + T.air.gases[/datum/gas/carbon_dioxide] -= amt + T.atmos_spawn_air("o2=[amt]") + +/mob/living/simple_animal/hostile/tree/AttackingTarget() + . = ..() + if(iscarbon(target)) + var/mob/living/carbon/C = target + if(prob(15)) + C.Knockdown(60) + C.visible_message("\The [src] knocks down \the [C]!", \ + "\The [src] knocks you down!") + +/mob/living/simple_animal/hostile/tree/festivus + name = "festivus pole" + desc = "Serenity now... SERENITY NOW!" + icon_state = "festivus_pole" + icon_living = "festivus_pole" + icon_dead = "festivus_pole" + icon_gib = "festivus_pole" + loot = list(/obj/item/stack/rods) + speak_emote = list("polls") diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index fcb201d0ad..2517d2438d 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1,589 +1,588 @@ -/mob/living/simple_animal - name = "animal" - icon = 'icons/mob/animal.dmi' - health = 20 - maxHealth = 20 - gender = PLURAL //placeholder - - status_flags = CANPUSH - - var/icon_living = "" - var/icon_dead = "" //icon when the animal is dead. Don't use animated icons for this. - var/icon_gib = null //We only try to show a gibbing animation if this exists. - - var/list/speak = list() - var/list/speak_emote = list()// Emotes while speaking IE: Ian [emote], [text] -- Ian barks, "WOOF!". Spoken text is generated from the speak variable. - var/speak_chance = 0 - var/list/emote_hear = list() //Hearable emotes - var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps - - var/turns_per_move = 1 - var/turns_since_move = 0 - var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. - var/wander = 1 // Does the mob wander around when idle? - var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. - - //Interaction - var/response_help = "pokes" - var/response_disarm = "shoves" - var/response_harm = "hits" - var/harm_intent_damage = 3 - var/force_threshold = 0 //Minimum force required to deal any damage - - //Temperature effect - var/minbodytemp = 250 - var/maxbodytemp = 350 - - //Healable by medical stacks? Defaults to yes. - var/healable = 1 - - //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage - var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum - var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above - - //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly - var/melee_damage_lower = 0 - var/melee_damage_upper = 0 - var/obj_damage = 0 //how much damage this simple animal does to objects, if any - var/armour_penetration = 0 //How much armour they ignore, as a flat reduction from the targets armour value - var/melee_damage_type = BRUTE //Damage type of a simple mob's melee attack, should it do damage. - var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) // 1 for full damage , 0 for none , -1 for 1:1 heal from that source - var/attacktext = "attacks" - var/attack_sound = null - var/friendly = "nuzzles" //If the mob does no damage with it's attack - var/environment_smash = ENVIRONMENT_SMASH_NONE //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls - - var/speed = 1 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster - - //Hot simple_animal baby making vars - var/list/childtype = null - var/next_scan_time = 0 - var/animal_species //Sorry, no spider+corgi buttbabies. - - //simple_animal access - var/obj/item/card/id/access_card = null //innate access uses an internal ID card - var/buffed = 0 //In the event that you want to have a buffing effect on the mob, but don't want it to stack with other effects, any outside force that applies a buff to a simple mob should at least set this to 1, so we have something to check against - var/gold_core_spawnable = NO_SPAWN //If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood - - var/mob/living/simple_animal/hostile/spawner/nest - - var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions - - var/list/loot = list() //list of things spawned at mob's loc when it dies - var/del_on_death = 0 //causes mob to be deleted on death, useful for mobs that spawn lootable corpses - var/deathmessage = "" - var/death_sound = null //The sound played on death - - var/allow_movement_on_non_turfs = FALSE - - var/attacked_sound = "punch" //Played when someone punches the creature - - var/dextrous = FALSE //If the creature has, and can use, hands - var/dextrous_hud_type = /datum/hud/dextrous - var/datum/personal_crafting/handcrafting - - var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever), AI_Z_OFF (Temporarily off due to nonpresence of players) - - var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check. - - //domestication - var/tame = 0 - - var/my_z // I don't want to confuse this with client registered_z - - var/do_footstep = FALSE - -/mob/living/simple_animal/Initialize() - . = ..() - GLOB.simple_animals[AIStatus] += src - handcrafting = new() - if(gender == PLURAL) - gender = pick(MALE,FEMALE) - if(!real_name) - real_name = name - if(!loc) - stack_trace("Simple animal being instantiated in nullspace") - update_simplemob_varspeed() - -/mob/living/simple_animal/Destroy() - GLOB.simple_animals[AIStatus] -= src - if (SSnpcpool.state == SS_PAUSED && LAZYLEN(SSnpcpool.currentrun)) - SSnpcpool.currentrun -= src - - if(nest) - nest.spawned_mobs -= src - nest = null - - var/turf/T = get_turf(src) - if (T && AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - - return ..() - -/mob/living/simple_animal/initialize_footstep() - if(do_footstep) - ..() - -/mob/living/simple_animal/updatehealth() - ..() - health = CLAMP(health, 0, maxHealth) - -/mob/living/simple_animal/update_stat() - if(status_flags & GODMODE) - return - if(stat != DEAD) - if(health <= 0) - death() - else - stat = CONSCIOUS - med_hud_set_status() - - -/mob/living/simple_animal/handle_status_effects() - ..() - if(stuttering) - stuttering = 0 - -/mob/living/simple_animal/proc/handle_automated_action() - set waitfor = FALSE - return - -/mob/living/simple_animal/proc/handle_automated_movement() - set waitfor = FALSE - if(!stop_automated_movement && wander) - if((isturf(src.loc) || allow_movement_on_non_turfs) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. - turns_since_move++ - if(turns_since_move >= turns_per_move) - if(!(stop_automated_movement_when_pulled && pulledby)) //Some animals don't move when pulled - var/anydir = pick(GLOB.cardinals) - if(Process_Spacemove(anydir)) - Move(get_step(src, anydir), anydir) - turns_since_move = 0 - return 1 - -/mob/living/simple_animal/proc/handle_automated_speech(var/override) - set waitfor = FALSE - if(speak_chance) - if(prob(speak_chance) || override) - if(speak && speak.len) - if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) - var/length = speak.len - if(emote_hear && emote_hear.len) - length += emote_hear.len - if(emote_see && emote_see.len) - length += emote_see.len - var/randomValue = rand(1,length) - if(randomValue <= speak.len) - say(pick(speak), forced = "poly") - else - randomValue -= speak.len - if(emote_see && randomValue <= emote_see.len) - emote("me [pick(emote_see)]", 1) - else - emote("me [pick(emote_hear)]", 2) - else - say(pick(speak), forced = "poly") - else - if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - emote("me", 1, pick(emote_see)) - if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) - emote("me", 2, pick(emote_hear)) - if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) - var/length = emote_hear.len + emote_see.len - var/pick = rand(1,length) - if(pick <= emote_see.len) - emote("me", 1, pick(emote_see)) - else - emote("me", 2, pick(emote_hear)) - - -/mob/living/simple_animal/proc/environment_is_safe(datum/gas_mixture/environment, check_temp = FALSE) - . = TRUE - - if(pulledby && pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) - . = FALSE //getting choked - - if(isturf(src.loc) && isopenturf(src.loc)) - var/turf/open/ST = src.loc - if(ST.air) - var/ST_gases = ST.air.gases - ST.air.assert_gases(arglist(GLOB.hardcoded_gases)) - - var/tox = ST_gases[/datum/gas/plasma][MOLES] - var/oxy = ST_gases[/datum/gas/oxygen][MOLES] - var/n2 = ST_gases[/datum/gas/nitrogen][MOLES] - var/co2 = ST_gases[/datum/gas/carbon_dioxide][MOLES] - - ST.air.garbage_collect() - - if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) - . = FALSE - else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) - . = FALSE - else if(atmos_requirements["min_tox"] && tox < atmos_requirements["min_tox"]) - . = FALSE - else if(atmos_requirements["max_tox"] && tox > atmos_requirements["max_tox"]) - . = FALSE - else if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) - . = FALSE - else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) - . = FALSE - else if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) - . = FALSE - else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) - . = FALSE - else - if(atmos_requirements["min_oxy"] || atmos_requirements["min_tox"] || atmos_requirements["min_n2"] || atmos_requirements["min_co2"]) - . = FALSE - - if(check_temp) - var/areatemp = get_temperature(environment) - if((areatemp < minbodytemp) || (areatemp > maxbodytemp)) - . = FALSE - - -/mob/living/simple_animal/handle_environment(datum/gas_mixture/environment) - var/atom/A = src.loc - if(isturf(A)) - var/areatemp = get_temperature(environment) - if( abs(areatemp - bodytemperature) > 5) - var/diff = areatemp - bodytemperature - diff = diff / 5 - adjust_bodytemperature(diff) - - if(!environment_is_safe(environment)) - adjustHealth(unsuitable_atmos_damage) - - handle_temperature_damage() - -/mob/living/simple_animal/proc/handle_temperature_damage() - if((bodytemperature < minbodytemp) || (bodytemperature > maxbodytemp)) - adjustHealth(unsuitable_atmos_damage) - -/mob/living/simple_animal/gib() - if(butcher_results) - var/atom/Tsec = drop_location() - for(var/path in butcher_results) - for(var/i in 1 to butcher_results[path]) - new path(Tsec) - ..() - -/mob/living/simple_animal/gib_animation() - if(icon_gib) - new /obj/effect/temp_visual/gib_animation/animal(loc, icon_gib) - -/mob/living/simple_animal/say_mod(input, message_mode) - if(speak_emote && speak_emote.len) - verb_say = pick(speak_emote) - . = ..() - -/mob/living/simple_animal/emote(act, m_type=1, message = null, intentional = FALSE) - if(stat) - return - if(act == "scream") - message = "makes a loud and pained whimper." //ugly hack to stop animals screaming when crushed :P - act = "me" - ..(act, m_type, message) - -/mob/living/simple_animal/proc/set_varspeed(var_value) - speed = var_value - update_simplemob_varspeed() - -/mob/living/simple_animal/proc/update_simplemob_varspeed() - if(speed == 0) - remove_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE) - add_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE, 100, multiplicative_slowdown = speed, override = TRUE) - -/mob/living/simple_animal/Stat() - ..() - if(statpanel("Status")) - stat(null, "Health: [round((health / maxHealth) * 100)]%") - return 1 - -/mob/living/simple_animal/proc/drop_loot() - if(loot.len) - for(var/i in loot) - new i(loc) - -/mob/living/simple_animal/death(gibbed) - movement_type &= ~FLYING - if(nest) - nest.spawned_mobs -= src - nest = null - drop_loot() - if(dextrous) - drop_all_held_items() - if(!gibbed) - if(death_sound) - playsound(get_turf(src),death_sound, 200, 1) - if(deathmessage || !del_on_death) - emote("deathgasp") - if(del_on_death) - ..() - //Prevent infinite loops if the mob Destroy() is overridden in such - //a manner as to cause a call to death() again - del_on_death = FALSE - qdel(src) - else - health = 0 - icon_state = icon_dead - density = FALSE - lying = 1 - ..() - -/mob/living/simple_animal/proc/CanAttack(atom/the_target) - if(see_invisible < the_target.invisibility) - return FALSE - if(ismob(the_target)) - var/mob/M = the_target - if(M.status_flags & GODMODE) - return FALSE - if (isliving(the_target)) - var/mob/living/L = the_target - if(L.stat != CONSCIOUS) - return FALSE - if (ismecha(the_target)) - var/obj/mecha/M = the_target - if (M.occupant) - return FALSE - return TRUE - -/mob/living/simple_animal/handle_fire() - return - -/mob/living/simple_animal/IgniteMob() - return FALSE - -/mob/living/simple_animal/ExtinguishMob() - return - -/mob/living/simple_animal/revive(full_heal = 0, admin_revive = 0) - if(..()) //successfully ressuscitated from death - icon = initial(icon) - icon_state = icon_living - density = initial(density) - lying = 0 - . = 1 - movement_type = initial(movement_type) - -/mob/living/simple_animal/proc/make_babies() // <3 <3 <3 - if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) - return - next_scan_time = world.time + 400 - var/alone = 1 - var/mob/living/simple_animal/partner - var/children = 0 - for(var/mob/M in view(7, src)) - if(M.stat != CONSCIOUS) //Check if it's conscious FIRST. - continue - else if(istype(M, childtype)) //Check for children SECOND. - children++ - else if(istype(M, animal_species)) - if(M.ckey) - continue - else if(!istype(M, childtype) && M.gender == MALE) //Better safe than sorry ;_; - partner = M - - else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us. - return //we never mate when not alone, so just abort early - - if(alone && partner && children < 3) - var/childspawn = pickweight(childtype) - var/turf/target = get_turf(loc) - if(target) - return new childspawn(target) - -/mob/living/simple_animal/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE) - if(incapacitated()) - to_chat(src, "You can't do that right now!") - return FALSE - if(be_close && !in_range(M, src)) - to_chat(src, "You are too far away!") - return FALSE - if(!(no_dextery || dextrous)) - to_chat(src, "You don't have the dexterity to do this!") - return FALSE - return TRUE - -/mob/living/simple_animal/stripPanelUnequip(obj/item/what, mob/who, where) - if(!canUseTopic(who, BE_CLOSE)) - return - else - ..() - -/mob/living/simple_animal/stripPanelEquip(obj/item/what, mob/who, where) - if(!canUseTopic(who, BE_CLOSE)) - return - else - ..() - -/mob/living/simple_animal/update_canmove(value_otherwise = TRUE) - if(IsUnconscious() || IsStun() || IsKnockdown() || stat || resting) - drop_all_held_items() - canmove = FALSE - else if(buckled) - canmove = FALSE - else - canmove = value_otherwise - update_transform() - update_action_buttons_icon() - return canmove - -/mob/living/simple_animal/update_transform() - var/matrix/ntransform = matrix(transform) //aka transform.Copy() - var/changed = 0 - - if(resize != RESIZE_DEFAULT_SIZE) - changed++ - ntransform.Scale(resize) - resize = RESIZE_DEFAULT_SIZE - - if(changed) - animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) - -/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion - toggle_ai(AI_OFF) // To prevent any weirdness. - -/mob/living/simple_animal/update_sight() - if(!client) - return - if(stat == DEAD) - sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) - see_in_dark = 8 - see_invisible = SEE_INVISIBLE_OBSERVER - return - - see_invisible = initial(see_invisible) - see_in_dark = initial(see_in_dark) - sight = initial(sight) - - if(client.eye != src) - var/atom/A = client.eye - if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. - return - sync_lighting_plane_alpha() - -/mob/living/simple_animal/get_idcard() - return access_card - -/mob/living/simple_animal/OpenCraftingMenu() - if(dextrous) - handcrafting.ui_interact(src) - -/mob/living/simple_animal/can_hold_items() - return dextrous - -/mob/living/simple_animal/IsAdvancedToolUser() - return dextrous - -/mob/living/simple_animal/activate_hand(selhand) - if(!dextrous) - return ..() - if(!selhand) - selhand = (active_hand_index % held_items.len)+1 - if(istext(selhand)) - selhand = lowertext(selhand) - if(selhand == "right" || selhand == "r") - selhand = 2 - if(selhand == "left" || selhand == "l") - selhand = 1 - if(selhand != active_hand_index) - swap_hand(selhand) - else - mode() - -/mob/living/simple_animal/swap_hand(hand_index) - if(!dextrous) - return ..() - if(!hand_index) - hand_index = (active_hand_index % held_items.len)+1 - var/obj/item/held_item = get_active_held_item() - if(held_item) - if(istype(held_item, /obj/item/twohanded)) - var/obj/item/twohanded/T = held_item - if(T.wielded == 1) - to_chat(usr, "Your other hand is too busy holding the [T.name].") - return - var/oindex = active_hand_index - active_hand_index = hand_index - if(hud_used) - var/obj/screen/inventory/hand/H - H = hud_used.hand_slots["[hand_index]"] - if(H) - H.update_icon() - H = hud_used.hand_slots["[oindex]"] - if(H) - H.update_icon() - -/mob/living/simple_animal/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE) - . = ..(I, del_on_fail, merge_stacks) - update_inv_hands() - -/mob/living/simple_animal/update_inv_hands() - if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) - var/obj/item/l_hand = get_item_for_held_index(1) - var/obj/item/r_hand = get_item_for_held_index(2) - if(r_hand) - r_hand.layer = ABOVE_HUD_LAYER - r_hand.plane = ABOVE_HUD_PLANE - r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) - client.screen |= r_hand - if(l_hand) - l_hand.layer = ABOVE_HUD_LAYER - l_hand.plane = ABOVE_HUD_PLANE - l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) - client.screen |= l_hand - -//ANIMAL RIDING - -/mob/living/simple_animal/user_buckle_mob(mob/living/M, mob/user) - GET_COMPONENT(riding_datum, /datum/component/riding) - if(riding_datum) - if(user.incapacitated()) - return - for(var/atom/movable/A in get_turf(src)) - if(A != src && A != M && A.density) - return - M.forceMove(get_turf(src)) - return ..() - -/mob/living/simple_animal/relaymove(mob/user, direction) - GET_COMPONENT(riding_datum, /datum/component/riding) - if(tame && riding_datum) - riding_datum.handle_ride(user, direction) - -/mob/living/simple_animal/buckle_mob(mob/living/buckled_mob, force = 0, check_loc = 1) - . = ..() - LoadComponent(/datum/component/riding) - -/mob/living/simple_animal/proc/toggle_ai(togglestatus) - if (AIStatus != togglestatus) - if (togglestatus > 0 && togglestatus < 5) - if (togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) - var/turf/T = get_turf(src) - if (AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src - else - SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src - GLOB.simple_animals[AIStatus] -= src - GLOB.simple_animals[togglestatus] += src - AIStatus = togglestatus - else - stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") - -/mob/living/simple_animal/proc/consider_wakeup() - if (pulledby || shouldwakeup) - toggle_ai(AI_ON) - -/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(!ckey && !stat)//Not unconscious - if(AIStatus == AI_IDLE) - toggle_ai(AI_ON) - - -/mob/living/simple_animal/onTransitZ(old_z, new_z) - ..() - if (AIStatus == AI_Z_OFF) - SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src - toggle_ai(initial(AIStatus)) +/mob/living/simple_animal + name = "animal" + icon = 'icons/mob/animal.dmi' + health = 20 + maxHealth = 20 + gender = PLURAL //placeholder + + status_flags = CANPUSH + + var/icon_living = "" + var/icon_dead = "" //icon when the animal is dead. Don't use animated icons for this. + var/icon_gib = null //We only try to show a gibbing animation if this exists. + + var/list/speak = list() + var/list/speak_emote = list()// Emotes while speaking IE: Ian [emote], [text] -- Ian barks, "WOOF!". Spoken text is generated from the speak variable. + var/speak_chance = 0 + var/list/emote_hear = list() //Hearable emotes + var/list/emote_see = list() //Unlike speak_emote, the list of things in this variable only show by themselves with no spoken text. IE: Ian barks, Ian yaps + + var/turns_per_move = 1 + var/turns_since_move = 0 + var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. + var/wander = 1 // Does the mob wander around when idle? + var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. + + //Interaction + var/response_help = "pokes" + var/response_disarm = "shoves" + var/response_harm = "hits" + var/harm_intent_damage = 3 + var/force_threshold = 0 //Minimum force required to deal any damage + + //Temperature effect + var/minbodytemp = 250 + var/maxbodytemp = 350 + + //Healable by medical stacks? Defaults to yes. + var/healable = 1 + + //Atmos effect - Yes, you can make creatures that require plasma or co2 to survive. N2O is a trace gas and handled separately, hence why it isn't here. It'd be hard to add it. Hard and me don't mix (Yes, yes make all the dick jokes you want with that.) - Errorage + var/list/atmos_requirements = list("min_oxy" = 5, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 1, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0) //Leaving something at 0 means it's off - has no maximum + var/unsuitable_atmos_damage = 2 //This damage is taken when atmos doesn't fit all the requirements above + + //LETTING SIMPLE ANIMALS ATTACK? WHAT COULD GO WRONG. Defaults to zero so Ian can still be cuddly + var/melee_damage_lower = 0 + var/melee_damage_upper = 0 + var/obj_damage = 0 //how much damage this simple animal does to objects, if any + var/armour_penetration = 0 //How much armour they ignore, as a flat reduction from the targets armour value + var/melee_damage_type = BRUTE //Damage type of a simple mob's melee attack, should it do damage. + var/list/damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) // 1 for full damage , 0 for none , -1 for 1:1 heal from that source + var/attacktext = "attacks" + var/attack_sound = null + var/friendly = "nuzzles" //If the mob does no damage with it's attack + var/environment_smash = ENVIRONMENT_SMASH_NONE //Set to 1 to allow breaking of crates,lockers,racks,tables; 2 for walls; 3 for Rwalls + + var/speed = 1 //LETS SEE IF I CAN SET SPEEDS FOR SIMPLE MOBS WITHOUT DESTROYING EVERYTHING. Higher speed is slower, negative speed is faster + + //Hot simple_animal baby making vars + var/list/childtype = null + var/next_scan_time = 0 + var/animal_species //Sorry, no spider+corgi buttbabies. + + //simple_animal access + var/obj/item/card/id/access_card = null //innate access uses an internal ID card + var/buffed = 0 //In the event that you want to have a buffing effect on the mob, but don't want it to stack with other effects, any outside force that applies a buff to a simple mob should at least set this to 1, so we have something to check against + var/gold_core_spawnable = NO_SPAWN //If the mob can be spawned with a gold slime core. HOSTILE_SPAWN are spawned with plasma, FRIENDLY_SPAWN are spawned with blood + + var/mob/living/simple_animal/hostile/spawner/nest + + var/sentience_type = SENTIENCE_ORGANIC // Sentience type, for slime potions + + var/list/loot = list() //list of things spawned at mob's loc when it dies + var/del_on_death = 0 //causes mob to be deleted on death, useful for mobs that spawn lootable corpses + var/deathmessage = "" + var/death_sound = null //The sound played on death + + var/allow_movement_on_non_turfs = FALSE + + var/attacked_sound = "punch" //Played when someone punches the creature + + var/dextrous = FALSE //If the creature has, and can use, hands + var/dextrous_hud_type = /datum/hud/dextrous + var/datum/personal_crafting/handcrafting + + var/AIStatus = AI_ON //The Status of our AI, can be set to AI_ON (On, usual processing), AI_IDLE (Will not process, but will return to AI_ON if an enemy comes near), AI_OFF (Off, Not processing ever), AI_Z_OFF (Temporarily off due to nonpresence of players) + + var/shouldwakeup = FALSE //convenience var for forcibly waking up an idling AI on next check. + + //domestication + var/tame = 0 + + var/my_z // I don't want to confuse this with client registered_z + + var/do_footstep = FALSE + +/mob/living/simple_animal/Initialize() + . = ..() + GLOB.simple_animals[AIStatus] += src + handcrafting = new() + if(gender == PLURAL) + gender = pick(MALE,FEMALE) + if(!real_name) + real_name = name + if(!loc) + stack_trace("Simple animal being instantiated in nullspace") + update_simplemob_varspeed() + +/mob/living/simple_animal/Destroy() + GLOB.simple_animals[AIStatus] -= src + if (SSnpcpool.state == SS_PAUSED && LAZYLEN(SSnpcpool.currentrun)) + SSnpcpool.currentrun -= src + + if(nest) + nest.spawned_mobs -= src + nest = null + + var/turf/T = get_turf(src) + if (T && AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src + + return ..() + +/mob/living/simple_animal/initialize_footstep() + if(do_footstep) + ..() + +/mob/living/simple_animal/updatehealth() + ..() + health = CLAMP(health, 0, maxHealth) + +/mob/living/simple_animal/update_stat() + if(status_flags & GODMODE) + return + if(stat != DEAD) + if(health <= 0) + death() + else + stat = CONSCIOUS + med_hud_set_status() + + +/mob/living/simple_animal/handle_status_effects() + ..() + if(stuttering) + stuttering = 0 + +/mob/living/simple_animal/proc/handle_automated_action() + set waitfor = FALSE + return + +/mob/living/simple_animal/proc/handle_automated_movement() + set waitfor = FALSE + if(!stop_automated_movement && wander) + if((isturf(src.loc) || allow_movement_on_non_turfs) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. + turns_since_move++ + if(turns_since_move >= turns_per_move) + if(!(stop_automated_movement_when_pulled && pulledby)) //Some animals don't move when pulled + var/anydir = pick(GLOB.cardinals) + if(Process_Spacemove(anydir)) + Move(get_step(src, anydir), anydir) + turns_since_move = 0 + return 1 + +/mob/living/simple_animal/proc/handle_automated_speech(var/override) + set waitfor = FALSE + if(speak_chance) + if(prob(speak_chance) || override) + if(speak && speak.len) + if((emote_hear && emote_hear.len) || (emote_see && emote_see.len)) + var/length = speak.len + if(emote_hear && emote_hear.len) + length += emote_hear.len + if(emote_see && emote_see.len) + length += emote_see.len + var/randomValue = rand(1,length) + if(randomValue <= speak.len) + say(pick(speak), forced = "poly") + else + randomValue -= speak.len + if(emote_see && randomValue <= emote_see.len) + emote("me [pick(emote_see)]", 1) + else + emote("me [pick(emote_hear)]", 2) + else + say(pick(speak), forced = "poly") + else + if(!(emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + emote("me", 1, pick(emote_see)) + if((emote_hear && emote_hear.len) && !(emote_see && emote_see.len)) + emote("me", 2, pick(emote_hear)) + if((emote_hear && emote_hear.len) && (emote_see && emote_see.len)) + var/length = emote_hear.len + emote_see.len + var/pick = rand(1,length) + if(pick <= emote_see.len) + emote("me", 1, pick(emote_see)) + else + emote("me", 2, pick(emote_hear)) + + +/mob/living/simple_animal/proc/environment_is_safe(datum/gas_mixture/environment, check_temp = FALSE) + . = TRUE + + if(pulledby && pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) + . = FALSE //getting choked + + if(isturf(src.loc) && isopenturf(src.loc)) + var/turf/open/ST = src.loc + if(ST.air) + var/ST_gases = ST.air.gases + + var/tox = ST_gases[/datum/gas/plasma] + var/oxy = ST_gases[/datum/gas/oxygen] + var/n2 = ST_gases[/datum/gas/nitrogen] + var/co2 = ST_gases[/datum/gas/carbon_dioxide] + + GAS_GARBAGE_COLLECT(ST.air.gases) + + if(atmos_requirements["min_oxy"] && oxy < atmos_requirements["min_oxy"]) + . = FALSE + else if(atmos_requirements["max_oxy"] && oxy > atmos_requirements["max_oxy"]) + . = FALSE + else if(atmos_requirements["min_tox"] && tox < atmos_requirements["min_tox"]) + . = FALSE + else if(atmos_requirements["max_tox"] && tox > atmos_requirements["max_tox"]) + . = FALSE + else if(atmos_requirements["min_n2"] && n2 < atmos_requirements["min_n2"]) + . = FALSE + else if(atmos_requirements["max_n2"] && n2 > atmos_requirements["max_n2"]) + . = FALSE + else if(atmos_requirements["min_co2"] && co2 < atmos_requirements["min_co2"]) + . = FALSE + else if(atmos_requirements["max_co2"] && co2 > atmos_requirements["max_co2"]) + . = FALSE + else + if(atmos_requirements["min_oxy"] || atmos_requirements["min_tox"] || atmos_requirements["min_n2"] || atmos_requirements["min_co2"]) + . = FALSE + + if(check_temp) + var/areatemp = get_temperature(environment) + if((areatemp < minbodytemp) || (areatemp > maxbodytemp)) + . = FALSE + + +/mob/living/simple_animal/handle_environment(datum/gas_mixture/environment) + var/atom/A = src.loc + if(isturf(A)) + var/areatemp = get_temperature(environment) + if( abs(areatemp - bodytemperature) > 5) + var/diff = areatemp - bodytemperature + diff = diff / 5 + adjust_bodytemperature(diff) + + if(!environment_is_safe(environment)) + adjustHealth(unsuitable_atmos_damage) + + handle_temperature_damage() + +/mob/living/simple_animal/proc/handle_temperature_damage() + if((bodytemperature < minbodytemp) || (bodytemperature > maxbodytemp)) + adjustHealth(unsuitable_atmos_damage) + +/mob/living/simple_animal/gib() + if(butcher_results) + var/atom/Tsec = drop_location() + for(var/path in butcher_results) + for(var/i in 1 to butcher_results[path]) + new path(Tsec) + ..() + +/mob/living/simple_animal/gib_animation() + if(icon_gib) + new /obj/effect/temp_visual/gib_animation/animal(loc, icon_gib) + +/mob/living/simple_animal/say_mod(input, message_mode) + if(speak_emote && speak_emote.len) + verb_say = pick(speak_emote) + . = ..() + +/mob/living/simple_animal/emote(act, m_type=1, message = null, intentional = FALSE) + if(stat) + return + if(act == "scream") + message = "makes a loud and pained whimper." //ugly hack to stop animals screaming when crushed :P + act = "me" + ..(act, m_type, message) + +/mob/living/simple_animal/proc/set_varspeed(var_value) + speed = var_value + update_simplemob_varspeed() + +/mob/living/simple_animal/proc/update_simplemob_varspeed() + if(speed == 0) + remove_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE) + add_movespeed_modifier(MOVESPEED_ID_SIMPLEMOB_VARSPEED, TRUE, 100, multiplicative_slowdown = speed, override = TRUE) + +/mob/living/simple_animal/Stat() + ..() + if(statpanel("Status")) + stat(null, "Health: [round((health / maxHealth) * 100)]%") + return 1 + +/mob/living/simple_animal/proc/drop_loot() + if(loot.len) + for(var/i in loot) + new i(loc) + +/mob/living/simple_animal/death(gibbed) + movement_type &= ~FLYING + if(nest) + nest.spawned_mobs -= src + nest = null + drop_loot() + if(dextrous) + drop_all_held_items() + if(!gibbed) + if(death_sound) + playsound(get_turf(src),death_sound, 200, 1) + if(deathmessage || !del_on_death) + emote("deathgasp") + if(del_on_death) + ..() + //Prevent infinite loops if the mob Destroy() is overridden in such + //a manner as to cause a call to death() again + del_on_death = FALSE + qdel(src) + else + health = 0 + icon_state = icon_dead + density = FALSE + lying = 1 + ..() + +/mob/living/simple_animal/proc/CanAttack(atom/the_target) + if(see_invisible < the_target.invisibility) + return FALSE + if(ismob(the_target)) + var/mob/M = the_target + if(M.status_flags & GODMODE) + return FALSE + if (isliving(the_target)) + var/mob/living/L = the_target + if(L.stat != CONSCIOUS) + return FALSE + if (ismecha(the_target)) + var/obj/mecha/M = the_target + if (M.occupant) + return FALSE + return TRUE + +/mob/living/simple_animal/handle_fire() + return + +/mob/living/simple_animal/IgniteMob() + return FALSE + +/mob/living/simple_animal/ExtinguishMob() + return + +/mob/living/simple_animal/revive(full_heal = 0, admin_revive = 0) + if(..()) //successfully ressuscitated from death + icon = initial(icon) + icon_state = icon_living + density = initial(density) + lying = 0 + . = 1 + movement_type = initial(movement_type) + +/mob/living/simple_animal/proc/make_babies() // <3 <3 <3 + if(gender != FEMALE || stat || next_scan_time > world.time || !childtype || !animal_species || !SSticker.IsRoundInProgress()) + return + next_scan_time = world.time + 400 + var/alone = 1 + var/mob/living/simple_animal/partner + var/children = 0 + for(var/mob/M in view(7, src)) + if(M.stat != CONSCIOUS) //Check if it's conscious FIRST. + continue + else if(istype(M, childtype)) //Check for children SECOND. + children++ + else if(istype(M, animal_species)) + if(M.ckey) + continue + else if(!istype(M, childtype) && M.gender == MALE) //Better safe than sorry ;_; + partner = M + + else if(isliving(M) && !faction_check_mob(M)) //shyness check. we're not shy in front of things that share a faction with us. + return //we never mate when not alone, so just abort early + + if(alone && partner && children < 3) + var/childspawn = pickweight(childtype) + var/turf/target = get_turf(loc) + if(target) + return new childspawn(target) + +/mob/living/simple_animal/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE) + if(incapacitated()) + to_chat(src, "You can't do that right now!") + return FALSE + if(be_close && !in_range(M, src)) + to_chat(src, "You are too far away!") + return FALSE + if(!(no_dextery || dextrous)) + to_chat(src, "You don't have the dexterity to do this!") + return FALSE + return TRUE + +/mob/living/simple_animal/stripPanelUnequip(obj/item/what, mob/who, where) + if(!canUseTopic(who, BE_CLOSE)) + return + else + ..() + +/mob/living/simple_animal/stripPanelEquip(obj/item/what, mob/who, where) + if(!canUseTopic(who, BE_CLOSE)) + return + else + ..() + +/mob/living/simple_animal/update_canmove(value_otherwise = TRUE) + if(IsUnconscious() || IsStun() || IsKnockdown() || stat || resting) + drop_all_held_items() + canmove = FALSE + else if(buckled) + canmove = FALSE + else + canmove = value_otherwise + update_transform() + update_action_buttons_icon() + return canmove + +/mob/living/simple_animal/update_transform() + var/matrix/ntransform = matrix(transform) //aka transform.Copy() + var/changed = 0 + + if(resize != RESIZE_DEFAULT_SIZE) + changed++ + ntransform.Scale(resize) + resize = RESIZE_DEFAULT_SIZE + + if(changed) + animate(src, transform = ntransform, time = 2, easing = EASE_IN|EASE_OUT) + +/mob/living/simple_animal/proc/sentience_act() //Called when a simple animal gains sentience via gold slime potion + toggle_ai(AI_OFF) // To prevent any weirdness. + +/mob/living/simple_animal/update_sight() + if(!client) + return + if(stat == DEAD) + sight = (SEE_TURFS|SEE_MOBS|SEE_OBJS) + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_OBSERVER + return + + see_invisible = initial(see_invisible) + see_in_dark = initial(see_in_dark) + sight = initial(sight) + + if(client.eye != src) + var/atom/A = client.eye + if(A.update_remote_sight(src)) //returns 1 if we override all other sight updates. + return + sync_lighting_plane_alpha() + +/mob/living/simple_animal/get_idcard() + return access_card + +/mob/living/simple_animal/OpenCraftingMenu() + if(dextrous) + handcrafting.ui_interact(src) + +/mob/living/simple_animal/can_hold_items() + return dextrous + +/mob/living/simple_animal/IsAdvancedToolUser() + return dextrous + +/mob/living/simple_animal/activate_hand(selhand) + if(!dextrous) + return ..() + if(!selhand) + selhand = (active_hand_index % held_items.len)+1 + if(istext(selhand)) + selhand = lowertext(selhand) + if(selhand == "right" || selhand == "r") + selhand = 2 + if(selhand == "left" || selhand == "l") + selhand = 1 + if(selhand != active_hand_index) + swap_hand(selhand) + else + mode() + +/mob/living/simple_animal/swap_hand(hand_index) + if(!dextrous) + return ..() + if(!hand_index) + hand_index = (active_hand_index % held_items.len)+1 + var/obj/item/held_item = get_active_held_item() + if(held_item) + if(istype(held_item, /obj/item/twohanded)) + var/obj/item/twohanded/T = held_item + if(T.wielded == 1) + to_chat(usr, "Your other hand is too busy holding the [T.name].") + return + var/oindex = active_hand_index + active_hand_index = hand_index + if(hud_used) + var/obj/screen/inventory/hand/H + H = hud_used.hand_slots["[hand_index]"] + if(H) + H.update_icon() + H = hud_used.hand_slots["[oindex]"] + if(H) + H.update_icon() + +/mob/living/simple_animal/put_in_hands(obj/item/I, del_on_fail = FALSE, merge_stacks = TRUE) + . = ..(I, del_on_fail, merge_stacks) + update_inv_hands() + +/mob/living/simple_animal/update_inv_hands() + if(client && hud_used && hud_used.hud_version != HUD_STYLE_NOHUD) + var/obj/item/l_hand = get_item_for_held_index(1) + var/obj/item/r_hand = get_item_for_held_index(2) + if(r_hand) + r_hand.layer = ABOVE_HUD_LAYER + r_hand.plane = ABOVE_HUD_PLANE + r_hand.screen_loc = ui_hand_position(get_held_index_of_item(r_hand)) + client.screen |= r_hand + if(l_hand) + l_hand.layer = ABOVE_HUD_LAYER + l_hand.plane = ABOVE_HUD_PLANE + l_hand.screen_loc = ui_hand_position(get_held_index_of_item(l_hand)) + client.screen |= l_hand + +//ANIMAL RIDING + +/mob/living/simple_animal/user_buckle_mob(mob/living/M, mob/user) + GET_COMPONENT(riding_datum, /datum/component/riding) + if(riding_datum) + if(user.incapacitated()) + return + for(var/atom/movable/A in get_turf(src)) + if(A != src && A != M && A.density) + return + M.forceMove(get_turf(src)) + return ..() + +/mob/living/simple_animal/relaymove(mob/user, direction) + GET_COMPONENT(riding_datum, /datum/component/riding) + if(tame && riding_datum) + riding_datum.handle_ride(user, direction) + +/mob/living/simple_animal/buckle_mob(mob/living/buckled_mob, force = 0, check_loc = 1) + . = ..() + LoadComponent(/datum/component/riding) + +/mob/living/simple_animal/proc/toggle_ai(togglestatus) + if (AIStatus != togglestatus) + if (togglestatus > 0 && togglestatus < 5) + if (togglestatus == AI_Z_OFF || AIStatus == AI_Z_OFF) + var/turf/T = get_turf(src) + if (AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[T.z] -= src + else + SSidlenpcpool.idle_mobs_by_zlevel[T.z] += src + GLOB.simple_animals[AIStatus] -= src + GLOB.simple_animals[togglestatus] += src + AIStatus = togglestatus + else + stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") + +/mob/living/simple_animal/proc/consider_wakeup() + if (pulledby || shouldwakeup) + toggle_ai(AI_ON) + +/mob/living/simple_animal/adjustHealth(amount, updating_health = TRUE, forced = FALSE) + . = ..() + if(!ckey && !stat)//Not unconscious + if(AIStatus == AI_IDLE) + toggle_ai(AI_ON) + + +/mob/living/simple_animal/onTransitZ(old_z, new_z) + ..() + if (AIStatus == AI_Z_OFF) + SSidlenpcpool.idle_mobs_by_zlevel[old_z] -= src + toggle_ai(initial(AIStatus)) diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index 161362c187..ebb34fe77a 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -132,7 +132,7 @@ if(stat != DEAD) var/bz_percentage =0 if(environment.gases[/datum/gas/bz]) - bz_percentage = environment.gases[/datum/gas/bz][MOLES] / environment.total_moles() + bz_percentage = environment.gases[/datum/gas/bz] / environment.total_moles() var/stasis = (bz_percentage >= 0.05 && bodytemperature < (T0C + 100)) || force_stasis if(stat == CONSCIOUS && stasis) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 9d662b1673..7ff8fbc0b1 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -67,8 +67,8 @@ t += "Temperature: [environment.temperature] \n" for(var/id in environment.gases) var/gas = environment.gases[id] - if(gas[MOLES]) - t+="[gas[GAS_META][META_GAS_NAME]]: [gas[MOLES]] \n" + if(gas) + t+="[GLOB.meta_gas_names[id]]: [gas] \n" to_chat(usr, t) diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index 138e8bb9dd..e7dd42a797 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -81,13 +81,13 @@ if(air.total_moles()) for(var/gasid in air.gases) gasdata.Add(list(list( - "name"= air.gases[gasid][GAS_META][META_GAS_NAME], - "amount" = round(100*air.gases[gasid][MOLES]/air.total_moles(),0.01)))) + "name"= GLOB.meta_gas_names[gasid], + "amount" = round(100*air.gases[gasid]/air.total_moles(),0.01)))) else for(var/gasid in air.gases) gasdata.Add(list(list( - "name"= air.gases[gasid][GAS_META][META_GAS_NAME], + "name"= GLOB.meta_gas_names[gasid], "amount" = 0))) data["gases"] = gasdata diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm index 3b9c3a549b..4159d9898a 100644 --- a/code/modules/power/singularity/collector.dm +++ b/code/modules/power/singularity/collector.dm @@ -43,11 +43,10 @@ playsound(src, 'sound/machines/ding.ogg', 50, 1) eject() else - var/gasdrained = min(powerproduction_drain*drainratio,loaded_tank.air_contents.gases[/datum/gas/plasma][MOLES]) - loaded_tank.air_contents.gases[/datum/gas/plasma][MOLES] -= gasdrained - loaded_tank.air_contents.assert_gas(/datum/gas/tritium) - loaded_tank.air_contents.gases[/datum/gas/tritium][MOLES] += gasdrained - loaded_tank.air_contents.garbage_collect() + var/gasdrained = min(powerproduction_drain*drainratio,loaded_tank.air_contents.gases[/datum/gas/plasma]) + loaded_tank.air_contents.gases[/datum/gas/plasma] -= gasdrained + loaded_tank.air_contents.gases[/datum/gas/tritium] += gasdrained + GAS_GARBAGE_COLLECT(loaded_tank.air_contents.gases) var/power_produced = RAD_COLLECTOR_OUTPUT add_avail(power_produced) @@ -58,11 +57,10 @@ eject() else var/gasdrained = bitcoinproduction_drain*drainratio - loaded_tank.air_contents.gases[/datum/gas/tritium][MOLES] -= gasdrained - loaded_tank.air_contents.gases[/datum/gas/oxygen][MOLES] -= gasdrained - loaded_tank.air_contents.assert_gas(/datum/gas/carbon_dioxide) - loaded_tank.air_contents.gases[/datum/gas/carbon_dioxide][MOLES] += gasdrained*2 - loaded_tank.air_contents.garbage_collect() + loaded_tank.air_contents.gases[/datum/gas/tritium] -= gasdrained + loaded_tank.air_contents.gases[/datum/gas/oxygen] -= gasdrained + loaded_tank.air_contents.gases[/datum/gas/carbon_dioxide] += gasdrained*2 + GAS_GARBAGE_COLLECT(loaded_tank.air_contents.gases) var/bitcoins_mined = RAD_COLLECTOR_OUTPUT SSresearch.science_tech.add_point_type(TECHWEB_POINT_TYPE_DEFAULT, bitcoins_mined*RAD_COLLECTOR_MINING_CONVERSION_RATE) stored_power-=bitcoins_mined @@ -76,7 +74,6 @@ var/fuel if(loaded_tank) fuel = loaded_tank.air_contents.gases[/datum/gas/plasma] - fuel = fuel ? fuel[MOLES] : 0 investigate_log("turned [active?"on":"off"] by [key_name(user)]. [loaded_tank?"Fuel: [round(fuel/0.29)]%":"It is empty"].", INVESTIGATE_SINGULO) return else diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 99b4a3f27e..d87f9821ef 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -351,16 +351,15 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(damage > damage_archived && prob(10)) playsound(get_turf(src), 'sound/effects/empulse.ogg', 50, 1) - removed.assert_gases(/datum/gas/oxygen, /datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/nitrogen) //calculating gas related values combined_gas = max(removed.total_moles(), 0) - plasmacomp = max(removed.gases[/datum/gas/plasma][MOLES]/combined_gas, 0) - o2comp = max(removed.gases[/datum/gas/oxygen][MOLES]/combined_gas, 0) - co2comp = max(removed.gases[/datum/gas/carbon_dioxide][MOLES]/combined_gas, 0) + plasmacomp = max(removed.gases[/datum/gas/plasma]/combined_gas, 0) + o2comp = max(removed.gases[/datum/gas/oxygen]/combined_gas, 0) + co2comp = max(removed.gases[/datum/gas/carbon_dioxide]/combined_gas, 0) - n2ocomp = max(removed.gases[/datum/gas/nitrous_oxide][MOLES]/combined_gas, 0) - n2comp = max(removed.gases[/datum/gas/nitrogen][MOLES]/combined_gas, 0) + n2ocomp = max(removed.gases[/datum/gas/nitrous_oxide]/combined_gas, 0) + n2comp = max(removed.gases[/datum/gas/nitrogen]/combined_gas, 0) gasmix_power_ratio = min(max(plasmacomp + o2comp + co2comp - n2comp, 0), 1) @@ -412,9 +411,9 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) removed.temperature = max(0, min(removed.temperature, 2500 * dynamic_heat_modifier)) //Calculate how much gas to release - removed.gases[/datum/gas/plasma][MOLES] += max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0) + removed.gases[/datum/gas/plasma] += max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0) - removed.gases[/datum/gas/oxygen][MOLES] += max(((device_energy + removed.temperature * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0) + removed.gases[/datum/gas/oxygen] += max(((device_energy + removed.temperature * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0) if(produces_gas) env.merge(removed) diff --git a/code/modules/research/xenobiology/crossbreeding/chilling.dm b/code/modules/research/xenobiology/crossbreeding/chilling.dm index 25dbaa461f..cfbbc8e4a1 100644 --- a/code/modules/research/xenobiology/crossbreeding/chilling.dm +++ b/code/modules/research/xenobiology/crossbreeding/chilling.dm @@ -101,10 +101,9 @@ Chilling extracts: for(var/turf/open/T in A) var/datum/gas_mixture/G = T.air if(istype(G)) - G.assert_gas(/datum/gas/plasma) - G.gases[/datum/gas/plasma][MOLES] = 0 + G.gases[/datum/gas/plasma] = 0 filtered = TRUE - G.garbage_collect() + GAS_GARBAGE_COLLECT(G.gases) T.air_update_turf() if(filtered) user.visible_message("Cracks spread throughout [src], and some air is sucked in!") diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 1e22796b1b..9a010e881c 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -84,13 +84,11 @@ var/list/breath_gases = breath.gases - breath.assert_gases(/datum/gas/oxygen, /datum/gas/plasma, /datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide, /datum/gas/bz, /datum/gas/nitrogen, /datum/gas/tritium, /datum/gas/nitryl, /datum/gas/pluoxium, /datum/gas/stimulum) - //Partial pressures in our breath - var/O2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen][MOLES])+(8*breath.get_breath_partial_pressure(breath_gases[/datum/gas/pluoxium][MOLES])) - var/N2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrogen][MOLES]) - var/Toxins_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma][MOLES]) - var/CO2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide][MOLES]) + var/O2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/oxygen])+(8*breath.get_breath_partial_pressure(breath_gases[/datum/gas/pluoxium])) + var/N2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrogen]) + var/Toxins_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/plasma]) + var/CO2_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/carbon_dioxide]) //-- OXY --// @@ -98,7 +96,7 @@ //Too much oxygen! //Yes, some species may not like it. if(safe_oxygen_max) if((O2_pp > safe_oxygen_max) && safe_oxygen_max == 0) //I guess plasma men technically need to have a check. - var/ratio = (breath_gases[/datum/gas/oxygen][MOLES]/safe_oxygen_max) * 10 + var/ratio = (breath_gases[/datum/gas/oxygen]/safe_oxygen_max) * 10 H.apply_damage_type(CLAMP(ratio, oxy_breath_dam_min, oxy_breath_dam_max), oxy_damage_type) H.throw_alert("too_much_oxy", /obj/screen/alert/too_much_oxy) @@ -121,18 +119,18 @@ //Too little oxygen! if(safe_oxygen_min) if(O2_pp < safe_oxygen_min) - gas_breathed = handle_too_little_breath(H, O2_pp, safe_oxygen_min, breath_gases[/datum/gas/oxygen][MOLES]) + gas_breathed = handle_too_little_breath(H, O2_pp, safe_oxygen_min, breath_gases[/datum/gas/oxygen]) H.throw_alert("not_enough_oxy", /obj/screen/alert/not_enough_oxy) else H.failed_last_breath = FALSE if(H.health >= H.crit_threshold) H.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/oxygen][MOLES] + gas_breathed = breath_gases[/datum/gas/oxygen] H.clear_alert("not_enough_oxy") //Exhale - breath_gases[/datum/gas/oxygen][MOLES] -= gas_breathed - breath_gases[/datum/gas/carbon_dioxide][MOLES] += gas_breathed + breath_gases[/datum/gas/oxygen] -= gas_breathed + breath_gases[/datum/gas/carbon_dioxide] += gas_breathed gas_breathed = 0 //-- Nitrogen --// @@ -140,7 +138,7 @@ //Too much nitrogen! if(safe_nitro_max) if(N2_pp > safe_nitro_max) - var/ratio = (breath_gases[/datum/gas/nitrogen][MOLES]/safe_nitro_max) * 10 + var/ratio = (breath_gases[/datum/gas/nitrogen]/safe_nitro_max) * 10 H.apply_damage_type(CLAMP(ratio, nitro_breath_dam_min, nitro_breath_dam_max), nitro_damage_type) H.throw_alert("too_much_nitro", /obj/screen/alert/too_much_nitro) H.losebreath += 2 @@ -150,18 +148,18 @@ //Too little nitrogen! if(safe_nitro_min) if(N2_pp < safe_nitro_min) - gas_breathed = handle_too_little_breath(H, N2_pp, safe_nitro_min, breath_gases[/datum/gas/nitrogen][MOLES]) + gas_breathed = handle_too_little_breath(H, N2_pp, safe_nitro_min, breath_gases[/datum/gas/nitrogen]) H.throw_alert("nitro", /obj/screen/alert/not_enough_nitro) else H.failed_last_breath = FALSE if(H.health >= H.crit_threshold) H.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/nitrogen][MOLES] + gas_breathed = breath_gases[/datum/gas/nitrogen] H.clear_alert("nitro") //Exhale - breath_gases[/datum/gas/nitrogen][MOLES] -= gas_breathed - breath_gases[/datum/gas/carbon_dioxide][MOLES] += gas_breathed + breath_gases[/datum/gas/nitrogen] -= gas_breathed + breath_gases[/datum/gas/carbon_dioxide] += gas_breathed gas_breathed = 0 //-- CO2 --// @@ -187,18 +185,18 @@ //Too little CO2! if(safe_co2_min) if(CO2_pp < safe_co2_min) - gas_breathed = handle_too_little_breath(H, CO2_pp, safe_co2_min, breath_gases[/datum/gas/carbon_dioxide][MOLES]) + gas_breathed = handle_too_little_breath(H, CO2_pp, safe_co2_min, breath_gases[/datum/gas/carbon_dioxide]) H.throw_alert("not_enough_co2", /obj/screen/alert/not_enough_co2) else H.failed_last_breath = FALSE if(H.health >= H.crit_threshold) H.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/carbon_dioxide][MOLES] + gas_breathed = breath_gases[/datum/gas/carbon_dioxide] H.clear_alert("not_enough_co2") //Exhale - breath_gases[/datum/gas/carbon_dioxide][MOLES] -= gas_breathed - breath_gases[/datum/gas/oxygen][MOLES] += gas_breathed + breath_gases[/datum/gas/carbon_dioxide] -= gas_breathed + breath_gases[/datum/gas/oxygen] += gas_breathed gas_breathed = 0 @@ -207,7 +205,7 @@ //Too much toxins! if(safe_toxins_max) if(Toxins_pp > safe_toxins_max) - var/ratio = (breath_gases[/datum/gas/plasma][MOLES]/safe_toxins_max) * 10 + var/ratio = (breath_gases[/datum/gas/plasma]/safe_toxins_max) * 10 H.apply_damage_type(CLAMP(ratio, tox_breath_dam_min, tox_breath_dam_max), tox_damage_type) H.throw_alert("too_much_tox", /obj/screen/alert/too_much_tox) else @@ -217,18 +215,18 @@ //Too little toxins! if(safe_toxins_min) if(Toxins_pp < safe_toxins_min) - gas_breathed = handle_too_little_breath(H, Toxins_pp, safe_toxins_min, breath_gases[/datum/gas/plasma][MOLES]) + gas_breathed = handle_too_little_breath(H, Toxins_pp, safe_toxins_min, breath_gases[/datum/gas/plasma]) H.throw_alert("not_enough_tox", /obj/screen/alert/not_enough_tox) else H.failed_last_breath = FALSE if(H.health >= H.crit_threshold) H.adjustOxyLoss(-5) - gas_breathed = breath_gases[/datum/gas/plasma][MOLES] + gas_breathed = breath_gases[/datum/gas/plasma] H.clear_alert("not_enough_tox") //Exhale - breath_gases[/datum/gas/plasma][MOLES] -= gas_breathed - breath_gases[/datum/gas/carbon_dioxide][MOLES] += gas_breathed + breath_gases[/datum/gas/plasma] -= gas_breathed + breath_gases[/datum/gas/carbon_dioxide] += gas_breathed gas_breathed = 0 @@ -238,7 +236,7 @@ // N2O - var/SA_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrous_oxide][MOLES]) + var/SA_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitrous_oxide]) if(SA_pp > SA_para_min) // Enough to make us stunned for a bit H.Unconscious(60) // 60 gives them one second to wake up and run away a bit! if(SA_pp > SA_sleep_min) // Enough to make us sleep as well @@ -249,7 +247,7 @@ // BZ - var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz][MOLES]) + var/bz_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/bz]) if(bz_pp > BZ_trip_balls_min) H.hallucination += 10 H.reagents.add_reagent("bz_metabolites",5) @@ -262,14 +260,14 @@ // Tritium - var/trit_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/tritium][MOLES]) + var/trit_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/tritium]) if (trit_pp > 50) H.radiation += trit_pp/2 //If you're breathing in half an atmosphere of radioactive gas, you fucked up. else H.radiation += trit_pp/10 // Nitryl - var/nitryl_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitryl][MOLES]) + var/nitryl_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/nitryl]) if (prob(nitryl_pp)) to_chat(H, "Your mouth feels like it's burning!") if (nitryl_pp >40) @@ -280,22 +278,22 @@ H.silent = max(H.silent, 3) else H.adjustFireLoss(nitryl_pp/4) - gas_breathed = breath_gases[/datum/gas/nitryl][MOLES] + gas_breathed = breath_gases[/datum/gas/nitryl] if (gas_breathed > gas_stimulation_min) H.reagents.add_reagent("no2",1) - breath_gases[/datum/gas/nitryl][MOLES]-=gas_breathed + breath_gases[/datum/gas/nitryl]-=gas_breathed // Stimulum - gas_breathed = breath_gases[/datum/gas/stimulum][MOLES] + gas_breathed = breath_gases[/datum/gas/stimulum] if (gas_breathed > gas_stimulation_min) var/existing = H.reagents.get_reagent_amount("stimulum") H.reagents.add_reagent("stimulum",max(0, 1 - existing)) - breath_gases[/datum/gas/stimulum][MOLES]-=gas_breathed + breath_gases[/datum/gas/stimulum]-=gas_breathed // Miasma if (breath_gases[/datum/gas/miasma]) - var/miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma][MOLES]) + var/miasma_pp = breath.get_breath_partial_pressure(breath_gases[/datum/gas/miasma]) //Miasma sickness if(prob(0.5 * miasma_pp)) @@ -334,14 +332,14 @@ // Then again, this is a purely hypothetical scenario and hardly reachable owner.adjust_disgust(0.1 * miasma_pp) - breath_gases[/datum/gas/miasma][MOLES]-=gas_breathed + breath_gases[/datum/gas/miasma]-=gas_breathed // Clear out moods when no miasma at all else SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "smell") handle_breath_temperature(breath, H) - breath.garbage_collect() + GAS_GARBAGE_COLLECT(breath.gases) return TRUE diff --git a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm index f0e5c35a8b..5e10e71433 100644 --- a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm +++ b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm @@ -1,390 +1,389 @@ -/* -DOG BORG EQUIPMENT HERE -SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! -*/ - -/obj/item/dogborg/jaws - name = "Dogborg jaws" - desc = "The jaws of the debug errors oh god." - icon = 'icons/mob/dogborg.dmi' - flags_1 = CONDUCT_1 - force = 1 - throwforce = 0 - w_class = 3 - hitsound = 'sound/weapons/bite.ogg' - sharpness = IS_SHARP - -/obj/item/dogborg/jaws/big - name = "combat jaws" - desc = "The jaws of the law. Very sharp." - icon_state = "jaws" - force = 12 - attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") - -/obj/item/dogborg/jaws/small - name = "puppy jaws" - desc = "Rubberized teeth designed to protect accidental harm. Sharp enough for specialized tasks however." - icon_state = "smalljaws" - force = 6 - attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") - var/status = 0 - -/obj/item/dogborg/jaws/attack(atom/A, mob/living/silicon/robot/user) - ..() - user.do_attack_animation(A, ATTACK_EFFECT_BITE) - log_combat(user, A, "bit") - -/obj/item/dogborg/jaws/small/attack_self(mob/user) - var/mob/living/silicon/robot.R = user - if(R.cell && R.cell.charge > 100) - if(R.emagged && status == 0) - name = "combat jaws" - icon_state = "jaws" - desc = "The jaws of the law." - force = 12 - attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") - status = 1 - to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") - else - name = "puppy jaws" - icon_state = "smalljaws" - desc = "The jaws of a small dog." - force = 5 - attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") - status = 0 - if(R.emagged) - to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") - update_icon() - -//Boop - -/obj/item/analyzer/nose - name = "boop module" - icon = 'icons/mob/dogborg.dmi' - icon_state = "nose" - desc = "The BOOP module" - flags_1 = CONDUCT_1 - force = 0 - throwforce = 0 - attack_verb = list("nuzzles", "pushes", "boops") - w_class = 1 - -/obj/item/analyzer/nose/attack_self(mob/user) - user.visible_message("[user] sniffs around the air.", "You sniff the air for gas traces.") - - var/turf/location = user.loc - if(!istype(location)) - return - - var/datum/gas_mixture/environment = location.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - to_chat(user, "Results:") - if(abs(pressure - ONE_ATMOSPHERE) < 10) - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - else - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - if(total_moles) - var/list/env_gases = environment.gases - - environment.assert_gases(arglist(GLOB.hardcoded_gases)) - var/o2_concentration = env_gases[/datum/gas/oxygen][MOLES]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen][MOLES]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide][MOLES]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma][MOLES]/total_moles - environment.garbage_collect() - - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") - else - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") - - if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") - else - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") - - if(co2_concentration > 0.01) - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") - else - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") - - if(plasma_concentration > 0.005) - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") - else - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") - - - for(var/id in env_gases) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = env_gases[id][MOLES]/total_moles - to_chat(user, "[env_gases[id][GAS_META][META_GAS_NAME]]: [round(gas_concentration*100, 0.01)] %") - to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") - -/obj/item/analyzer/nose/AltClick(mob/user) //Barometer output for measuring when the next storm happens - . = ..() - -/obj/item/analyzer/nose/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity) - return - do_attack_animation(target, null, src) - user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") - -//Delivery -/obj/item/storage/bag/borgdelivery - name = "fetching storage" - desc = "Fetch the thing!" - icon = 'icons/mob/dogborg.dmi' - icon_state = "dbag" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/borgdelivery/ComponentInitialize() - . = ..() - GET_COMPONENT(STR, /datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.max_combined_w_class = 5 - STR.max_items = 1 - STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear, /obj/item/radio/intercom)) - -//Tongue stuff -/obj/item/soap/tongue - name = "synthetic tongue" - desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." - icon = 'icons/mob/dogborg.dmi' - icon_state = "synthtongue" - hitsound = 'sound/effects/attackblob.ogg' - cleanspeed = 80 - var/status = 0 - -/obj/item/soap/tongue/scrubpup - cleanspeed = 25 //slightly faster than a mop. - -/obj/item/soap/tongue/New() - ..() - item_flags |= NOBLUDGEON //No more attack messages - -/obj/item/soap/tongue/attack_self(mob/user) - var/mob/living/silicon/robot.R = user - if(R.cell && R.cell.charge > 100) - if(R.emagged && status == 0) - status = !status - name = "energized tongue" - desc = "Your tongue is energized for dangerously maximum efficency." - icon_state = "syndietongue" - to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") - cleanspeed = 10 //(nerf'd)tator soap stat - else - status = 0 - name = "synthetic tongue" - desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." - icon_state = "synthtongue" - cleanspeed = initial(cleanspeed) - if(R.emagged) - to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") - update_icon() - -/obj/item/soap/tongue/afterattack(atom/target, mob/user, proximity) - var/mob/living/silicon/robot.R = user - if(!proximity || !check_allowed_items(target)) - return - if(R.client && (target in R.client.screen)) - to_chat(R, "You need to take that [target.name] off before cleaning it!") - else if(is_cleanable(target)) - R.visible_message("[R] begins to lick off \the [target.name].", "You begin to lick off \the [target.name]...") - if(do_after(R, src.cleanspeed, target = target)) - if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. - return //If they moved away, you can't eat them. - to_chat(R, "You finish licking off \the [target.name].") - qdel(target) - R.cell.give(50) - else if(isobj(target)) //hoo boy. danger zone man - if(istype(target,/obj/item/trash)) - R.visible_message("[R] nibbles away at \the [target.name].", "You begin to nibble away at \the [target.name]...") - if(!do_after(R, src.cleanspeed, target = target)) - return //If they moved away, you can't eat them. - to_chat(R, "You finish off \the [target.name].") - qdel(target) - R.cell.give(250) - return - if(istype(target,/obj/item/stock_parts/cell)) - R.visible_message("[R] begins cramming \the [target.name] down its throat.", "You begin cramming \the [target.name] down your throat...") - if(!do_after(R, 50, target = target)) - return //If they moved away, you can't eat them. - to_chat(R, "You finish off \the [target.name].") - var/obj/item/stock_parts/cell.C = target - R.cell.charge = R.cell.charge + (C.charge / 3) //Instant full cell upgrades op idgaf - qdel(target) - return - var/obj/item/I = target //HAHA FUCK IT, NOT LIKE WE ALREADY HAVE A SHITTON OF WAYS TO REMOVE SHIT - if(!I.anchored && R.emagged) - R.visible_message("[R] begins chewing up \the [target.name]. Looks like it's trying to loophole around its diet restriction!", "You begin chewing up \the [target.name]...") - if(!do_after(R, 100, target = I)) //Nerf dat time yo - return //If they moved away, you can't eat them. - visible_message("[R] chews up \the [target.name] and cleans off the debris!") - to_chat(R, "You finish off \the [target.name].") - qdel(I) - R.cell.give(500) - return - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - else if(ishuman(target)) - var/mob/living/L = target - if(status == 0 && check_zone(R.zone_selected) == "head") - R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - if(istype(L) && L.fire_stacks > 0) - L.adjust_fire_stacks(-10) - return - else if(status == 0) - R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - if(istype(L) && L.fire_stacks > 0) - L.adjust_fire_stacks(-10) - return - else - if(R.cell.charge <= 800) - to_chat(R, "Insufficent Power!") - return - L.Stun(4) // normal stunbaton is force 7 gimme a break good sir! - L.Knockdown(80) - L.apply_effect(EFFECT_STUTTER, 4) - L.visible_message("[R] has shocked [L] with its tongue!", \ - "[R] has shocked you with its tongue!") - playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) - R.cell.use(666) - log_combat(R, L, "tongue stunned") - - else if(istype(target, /obj/structure/window)) - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - if(do_after(user, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - target.set_opacity(initial(target.opacity)) - else - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - if(do_after(user, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - var/obj/effect/decal/cleanable/C = locate() in target - qdel(C) - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) - target.wash_cream() - return - -//Dogfood - -/obj/item/trash/rkibble - name = "robo kibble" - desc = "A novelty bowl of assorted mech fabricator byproducts. Mockingly feed this to the sec-dog to help it recharge." - icon = 'icons/mob/dogborg.dmi' - icon_state= "kibble" - -//Defibs - -/obj/item/twohanded/shockpaddles/cyborg/hound - name = "Paws of Life" - desc = "MediHound specific shock paws." - icon = 'icons/mob/dogborg.dmi' - icon_state = "defibpaddles0" - item_state = "defibpaddles0" - -// Pounce stuff for K-9 - -/obj/item/dogborg/pounce - name = "pounce" - icon = 'icons/mob/dogborg.dmi' - icon_state = "pounce" - desc = "Leap at your target to momentarily stun them." - force = 0 - throwforce = 0 - -/obj/item/dogborg/pounce/New() - ..() - item_flags |= NOBLUDGEON - -/mob/living/silicon/robot - var/leaping = 0 - var/pounce_cooldown = 0 - var/pounce_cooldown_time = 50 //Nearly doubled, u happy? - var/pounce_spoolup = 3 - var/leap_at - var/disabler - var/laser - var/sleeper_g - var/sleeper_r - var/sleeper_nv - -#define MAX_K9_LEAP_DIST 4 //because something's definitely borked the pounce functioning from a distance. - -/obj/item/dogborg/pounce/afterattack(atom/A, mob/user) - var/mob/living/silicon/robot/R = user - if(R && !R.pounce_cooldown) - R.pounce_cooldown = !R.pounce_cooldown - to_chat(R, "Your targeting systems lock on to [A]...") - addtimer(CALLBACK(R, /mob/living/silicon/robot.proc/leap_at, A), R.pounce_spoolup) - spawn(R.pounce_cooldown_time) - R.pounce_cooldown = !R.pounce_cooldown - else if(R && R.pounce_cooldown) - to_chat(R, "Your leg actuators are still recharging!") - -/mob/living/silicon/robot/proc/leap_at(atom/A) - if(leaping || stat || buckled || lying) - return - - if(!has_gravity(src) || !has_gravity(A)) - to_chat(src,"It is unsafe to leap without gravity!") - //It's also extremely buggy visually, so it's balance+bugfix - return - - if(cell.charge <= 500) - to_chat(src,"Insufficent reserves for jump actuators!") - return - - else - leaping = 1 - weather_immunities += "lava" - pixel_y = 10 - update_icons() - throw_at(A, MAX_K9_LEAP_DIST, 1, spin=0, diagonals_first = 1) - cell.use(500) //Doubled the energy consumption - weather_immunities -= "lava" - -/mob/living/silicon/robot/throw_impact(atom/A) - - if(!leaping) - return ..() - - if(A) - if(isliving(A)) - var/mob/living/L = A - var/blocked = 0 - if(ishuman(A)) - var/mob/living/carbon/human/H = A - if(H.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) - blocked = 1 - if(!blocked) - L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") - L.Knockdown(iscarbon(L) ? 450 : 45) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. - playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) - sleep(2)//Runtime prevention (infinite bump() calls on hulks) - step_towards(src,L) - log_combat(src, L, "borg pounced") - else - Knockdown(45, 1, 1) - - pounce_cooldown = !pounce_cooldown - spawn(pounce_cooldown_time) //3s by default - pounce_cooldown = !pounce_cooldown - else if(A.density && !A.CanPass(src)) - visible_message("[src] smashes into [A]!", "You smash into [A]!") - playsound(src, 'sound/items/trayhit1.ogg', 50, 1) - Knockdown(45, 1, 1) - - if(leaping) - leaping = 0 - pixel_y = initial(pixel_y) - update_icons() - update_canmove() +/* +DOG BORG EQUIPMENT HERE +SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! +*/ + +/obj/item/dogborg/jaws + name = "Dogborg jaws" + desc = "The jaws of the debug errors oh god." + icon = 'icons/mob/dogborg.dmi' + flags_1 = CONDUCT_1 + force = 1 + throwforce = 0 + w_class = 3 + hitsound = 'sound/weapons/bite.ogg' + sharpness = IS_SHARP + +/obj/item/dogborg/jaws/big + name = "combat jaws" + desc = "The jaws of the law. Very sharp." + icon_state = "jaws" + force = 12 + attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") + +/obj/item/dogborg/jaws/small + name = "puppy jaws" + desc = "Rubberized teeth designed to protect accidental harm. Sharp enough for specialized tasks however." + icon_state = "smalljaws" + force = 6 + attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") + var/status = 0 + +/obj/item/dogborg/jaws/attack(atom/A, mob/living/silicon/robot/user) + ..() + user.do_attack_animation(A, ATTACK_EFFECT_BITE) + log_combat(user, A, "bit") + +/obj/item/dogborg/jaws/small/attack_self(mob/user) + var/mob/living/silicon/robot.R = user + if(R.cell && R.cell.charge > 100) + if(R.emagged && status == 0) + name = "combat jaws" + icon_state = "jaws" + desc = "The jaws of the law." + force = 12 + attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") + status = 1 + to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") + else + name = "puppy jaws" + icon_state = "smalljaws" + desc = "The jaws of a small dog." + force = 5 + attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") + status = 0 + if(R.emagged) + to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") + update_icon() + +//Boop + +/obj/item/analyzer/nose + name = "boop module" + icon = 'icons/mob/dogborg.dmi' + icon_state = "nose" + desc = "The BOOP module" + flags_1 = CONDUCT_1 + force = 0 + throwforce = 0 + attack_verb = list("nuzzles", "pushes", "boops") + w_class = 1 + +/obj/item/analyzer/nose/attack_self(mob/user) + user.visible_message("[user] sniffs around the air.", "You sniff the air for gas traces.") + + var/turf/location = user.loc + if(!istype(location)) + return + + var/datum/gas_mixture/environment = location.return_air() + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + to_chat(user, "Results:") + if(abs(pressure - ONE_ATMOSPHERE) < 10) + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + else + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + if(total_moles) + var/list/env_gases = environment.gases + + var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles + var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles + var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles + var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles + GAS_GARBAGE_COLLECT(environment.gases) + + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") + else + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") + + if(abs(o2_concentration - O2STANDARD) < 2) + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") + else + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") + + if(co2_concentration > 0.01) + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") + else + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") + + if(plasma_concentration > 0.005) + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") + else + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") + + + for(var/id in env_gases) + if(id in GLOB.hardcoded_gases) + continue + var/gas_concentration = env_gases[id]/total_moles + to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] %") + to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") + +/obj/item/analyzer/nose/AltClick(mob/user) //Barometer output for measuring when the next storm happens + . = ..() + +/obj/item/analyzer/nose/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity) + return + do_attack_animation(target, null, src) + user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") + +//Delivery +/obj/item/storage/bag/borgdelivery + name = "fetching storage" + desc = "Fetch the thing!" + icon = 'icons/mob/dogborg.dmi' + icon_state = "dbag" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/borgdelivery/ComponentInitialize() + . = ..() + GET_COMPONENT(STR, /datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.max_combined_w_class = 5 + STR.max_items = 1 + STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear, /obj/item/radio/intercom)) + +//Tongue stuff +/obj/item/soap/tongue + name = "synthetic tongue" + desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." + icon = 'icons/mob/dogborg.dmi' + icon_state = "synthtongue" + hitsound = 'sound/effects/attackblob.ogg' + cleanspeed = 80 + var/status = 0 + +/obj/item/soap/tongue/scrubpup + cleanspeed = 25 //slightly faster than a mop. + +/obj/item/soap/tongue/New() + ..() + item_flags |= NOBLUDGEON //No more attack messages + +/obj/item/soap/tongue/attack_self(mob/user) + var/mob/living/silicon/robot.R = user + if(R.cell && R.cell.charge > 100) + if(R.emagged && status == 0) + status = !status + name = "energized tongue" + desc = "Your tongue is energized for dangerously maximum efficency." + icon_state = "syndietongue" + to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") + cleanspeed = 10 //(nerf'd)tator soap stat + else + status = 0 + name = "synthetic tongue" + desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." + icon_state = "synthtongue" + cleanspeed = initial(cleanspeed) + if(R.emagged) + to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") + update_icon() + +/obj/item/soap/tongue/afterattack(atom/target, mob/user, proximity) + var/mob/living/silicon/robot.R = user + if(!proximity || !check_allowed_items(target)) + return + if(R.client && (target in R.client.screen)) + to_chat(R, "You need to take that [target.name] off before cleaning it!") + else if(is_cleanable(target)) + R.visible_message("[R] begins to lick off \the [target.name].", "You begin to lick off \the [target.name]...") + if(do_after(R, src.cleanspeed, target = target)) + if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. + return //If they moved away, you can't eat them. + to_chat(R, "You finish licking off \the [target.name].") + qdel(target) + R.cell.give(50) + else if(isobj(target)) //hoo boy. danger zone man + if(istype(target,/obj/item/trash)) + R.visible_message("[R] nibbles away at \the [target.name].", "You begin to nibble away at \the [target.name]...") + if(!do_after(R, src.cleanspeed, target = target)) + return //If they moved away, you can't eat them. + to_chat(R, "You finish off \the [target.name].") + qdel(target) + R.cell.give(250) + return + if(istype(target,/obj/item/stock_parts/cell)) + R.visible_message("[R] begins cramming \the [target.name] down its throat.", "You begin cramming \the [target.name] down your throat...") + if(!do_after(R, 50, target = target)) + return //If they moved away, you can't eat them. + to_chat(R, "You finish off \the [target.name].") + var/obj/item/stock_parts/cell.C = target + R.cell.charge = R.cell.charge + (C.charge / 3) //Instant full cell upgrades op idgaf + qdel(target) + return + var/obj/item/I = target //HAHA FUCK IT, NOT LIKE WE ALREADY HAVE A SHITTON OF WAYS TO REMOVE SHIT + if(!I.anchored && R.emagged) + R.visible_message("[R] begins chewing up \the [target.name]. Looks like it's trying to loophole around its diet restriction!", "You begin chewing up \the [target.name]...") + if(!do_after(R, 100, target = I)) //Nerf dat time yo + return //If they moved away, you can't eat them. + visible_message("[R] chews up \the [target.name] and cleans off the debris!") + to_chat(R, "You finish off \the [target.name].") + qdel(I) + R.cell.give(500) + return + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + else if(ishuman(target)) + var/mob/living/L = target + if(status == 0 && check_zone(R.zone_selected) == "head") + R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + if(istype(L) && L.fire_stacks > 0) + L.adjust_fire_stacks(-10) + return + else if(status == 0) + R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + if(istype(L) && L.fire_stacks > 0) + L.adjust_fire_stacks(-10) + return + else + if(R.cell.charge <= 800) + to_chat(R, "Insufficent Power!") + return + L.Stun(4) // normal stunbaton is force 7 gimme a break good sir! + L.Knockdown(80) + L.apply_effect(EFFECT_STUTTER, 4) + L.visible_message("[R] has shocked [L] with its tongue!", \ + "[R] has shocked you with its tongue!") + playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) + R.cell.use(666) + log_combat(R, L, "tongue stunned") + + else if(istype(target, /obj/structure/window)) + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + if(do_after(user, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + target.set_opacity(initial(target.opacity)) + else + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + if(do_after(user, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + var/obj/effect/decal/cleanable/C = locate() in target + qdel(C) + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + target.wash_cream() + return + +//Dogfood + +/obj/item/trash/rkibble + name = "robo kibble" + desc = "A novelty bowl of assorted mech fabricator byproducts. Mockingly feed this to the sec-dog to help it recharge." + icon = 'icons/mob/dogborg.dmi' + icon_state= "kibble" + +//Defibs + +/obj/item/twohanded/shockpaddles/cyborg/hound + name = "Paws of Life" + desc = "MediHound specific shock paws." + icon = 'icons/mob/dogborg.dmi' + icon_state = "defibpaddles0" + item_state = "defibpaddles0" + +// Pounce stuff for K-9 + +/obj/item/dogborg/pounce + name = "pounce" + icon = 'icons/mob/dogborg.dmi' + icon_state = "pounce" + desc = "Leap at your target to momentarily stun them." + force = 0 + throwforce = 0 + +/obj/item/dogborg/pounce/New() + ..() + item_flags |= NOBLUDGEON + +/mob/living/silicon/robot + var/leaping = 0 + var/pounce_cooldown = 0 + var/pounce_cooldown_time = 50 //Nearly doubled, u happy? + var/pounce_spoolup = 3 + var/leap_at + var/disabler + var/laser + var/sleeper_g + var/sleeper_r + var/sleeper_nv + +#define MAX_K9_LEAP_DIST 4 //because something's definitely borked the pounce functioning from a distance. + +/obj/item/dogborg/pounce/afterattack(atom/A, mob/user) + var/mob/living/silicon/robot/R = user + if(R && !R.pounce_cooldown) + R.pounce_cooldown = !R.pounce_cooldown + to_chat(R, "Your targeting systems lock on to [A]...") + addtimer(CALLBACK(R, /mob/living/silicon/robot.proc/leap_at, A), R.pounce_spoolup) + spawn(R.pounce_cooldown_time) + R.pounce_cooldown = !R.pounce_cooldown + else if(R && R.pounce_cooldown) + to_chat(R, "Your leg actuators are still recharging!") + +/mob/living/silicon/robot/proc/leap_at(atom/A) + if(leaping || stat || buckled || lying) + return + + if(!has_gravity(src) || !has_gravity(A)) + to_chat(src,"It is unsafe to leap without gravity!") + //It's also extremely buggy visually, so it's balance+bugfix + return + + if(cell.charge <= 500) + to_chat(src,"Insufficent reserves for jump actuators!") + return + + else + leaping = 1 + weather_immunities += "lava" + pixel_y = 10 + update_icons() + throw_at(A, MAX_K9_LEAP_DIST, 1, spin=0, diagonals_first = 1) + cell.use(500) //Doubled the energy consumption + weather_immunities -= "lava" + +/mob/living/silicon/robot/throw_impact(atom/A) + + if(!leaping) + return ..() + + if(A) + if(isliving(A)) + var/mob/living/L = A + var/blocked = 0 + if(ishuman(A)) + var/mob/living/carbon/human/H = A + if(H.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) + blocked = 1 + if(!blocked) + L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") + L.Knockdown(iscarbon(L) ? 450 : 45) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. + playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) + sleep(2)//Runtime prevention (infinite bump() calls on hulks) + step_towards(src,L) + log_combat(src, L, "borg pounced") + else + Knockdown(45, 1, 1) + + pounce_cooldown = !pounce_cooldown + spawn(pounce_cooldown_time) //3s by default + pounce_cooldown = !pounce_cooldown + else if(A.density && !A.CanPass(src)) + visible_message("[src] smashes into [A]!", "You smash into [A]!") + playsound(src, 'sound/items/trayhit1.ogg', 50, 1) + Knockdown(45, 1, 1) + + if(leaping) + leaping = 0 + pixel_y = initial(pixel_y) + update_icons() + update_canmove()