diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index ae79360dd2..928837979c 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -239,6 +239,8 @@ //HELPERS #define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity()) +#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)\ @@ -247,6 +249,14 @@ 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( diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm index 1118ff3609..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]) //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) - 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] = 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 08921ecb8b..79deac475b 100644 --- a/code/game/objects/effects/effect_system/effects_smoke.dm +++ b/code/game/objects/effects/effect_system/effects_smoke.dm @@ -1,328 +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_gases[/datum/gas/nitrogen] += (G_gases[/datum/gas/plasma]) - G_gases[/datum/gas/plasma] = 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/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 20262d5052..37243acc40 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -444,7 +444,7 @@ SLIME SCANNER else 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) diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm index 79bf8f279e..3f9ee9c9e5 100644 --- a/code/game/turfs/open.dm +++ b/code/game/turfs/open.dm @@ -1,296 +1,296 @@ -/turf/open - plane = FLOOR_PLANE - var/slowdown = 0 //negative for faster, positive for slower - - var/postdig_icon_change = FALSE - var/postdig_icon - var/wet - - var/footstep = null - -/turf/open/ComponentInitialize() - . = ..() - if(wet) - AddComponent(/datum/component/wet_floor, wet, INFINITY, 0, INFINITY, TRUE) - -/turf/open/MouseDrop_T(atom/dropping, mob/user) - . = ..() - if(dropping == user && isliving(user)) - var/mob/living/L = user - if(L.resting && do_after(L, max(10, L.getStaminaLoss()*0.5), 0, src)) - if(Adjacent(L, src)) - step(L, get_dir(L, src)) - playsound(L, "rustle", 25, 1) - -/turf/open/indestructible - name = "floor" - icon = 'icons/turf/floors.dmi' - icon_state = "floor" - footstep = FOOTSTEP_FLOOR - tiled_dirt = TRUE - -/turf/open/indestructible/Melt() - to_be_destroyed = FALSE - return src - -/turf/open/indestructible/singularity_act() - return - -/turf/open/indestructible/TerraformTurf(path, defer_change = FALSE, ignore_air = FALSE) - return - -/turf/open/indestructible/sound - name = "squeaky floor" - footstep = null - var/sound - -/turf/open/indestructible/sound/Entered(var/mob/AM) - ..() - if(istype(AM)) - playsound(src,sound,50,1) - -/turf/open/indestructible/cobble/side - icon_state = "cobble_side" - -/turf/open/indestructible/cobble/corner - icon_state = "cobble_corner" - -/turf/open/indestructible/cobble - name = "cobblestone path" - desc = "A simple but beautiful path made of various sized stones." - icon = 'icons/turf/floors.dmi' - icon_state = "cobble" - baseturfs = /turf/open/indestructible/cobble - tiled_dirt = FALSE - -/turf/open/indestructible/necropolis - name = "necropolis floor" - desc = "It's regarding you suspiciously." - icon = 'icons/turf/floors.dmi' - icon_state = "necro1" - baseturfs = /turf/open/indestructible/necropolis - initial_gas_mix = LAVALAND_DEFAULT_ATMOS - footstep = FOOTSTEP_LAVA - tiled_dirt = FALSE - -/turf/open/indestructible/necropolis/Initialize() - . = ..() - if(prob(12)) - icon_state = "necro[rand(2,3)]" - -/turf/open/indestructible/necropolis/air - initial_gas_mix = "o2=22;n2=82;TEMP=293.15" - -/turf/open/indestructible/boss //you put stone tiles on this and use it as a base - name = "necropolis floor" - icon = 'icons/turf/boss_floors.dmi' - icon_state = "boss" - baseturfs = /turf/open/indestructible/boss - initial_gas_mix = LAVALAND_DEFAULT_ATMOS - -/turf/open/indestructible/boss/air - initial_gas_mix = "o2=22;n2=82;TEMP=293.15" - -/turf/open/indestructible/hierophant - icon = 'icons/turf/floors/hierophant_floor.dmi' - initial_gas_mix = LAVALAND_DEFAULT_ATMOS - baseturfs = /turf/open/indestructible/hierophant - smooth = SMOOTH_TRUE - tiled_dirt = FALSE - -/turf/open/indestructible/hierophant/two - -/turf/open/indestructible/hierophant/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) - return FALSE - -/turf/open/indestructible/paper - name = "notebook floor" - desc = "A floor made of invulnerable notebook paper." - icon_state = "paperfloor" - footstep = null - tiled_dirt = FALSE - -/turf/open/indestructible/binary - name = "tear in the fabric of reality" - CanAtmosPass = ATMOS_PASS_NO - baseturfs = /turf/open/indestructible/binary - icon_state = "binary" - footstep = null - -/turf/open/indestructible/airblock - icon_state = "bluespace" - CanAtmosPass = ATMOS_PASS_NO - baseturfs = /turf/open/indestructible/airblock - -/turf/open/indestructible/clock_spawn_room - name = "cogmetal floor" - desc = "Brass plating that gently radiates heat. For some reason, it reminds you of blood." - icon_state = "reebe" - baseturfs = /turf/open/indestructible/clock_spawn_room - footstep = FOOTSTEP_PLATING - -/turf/open/indestructible/clock_spawn_room/Entered() - ..() - START_PROCESSING(SSfastprocess, src) - -/turf/open/indestructible/clock_spawn_room/Destroy() - STOP_PROCESSING(SSfastprocess, src) - . = ..() - -/turf/open/indestructible/clock_spawn_room/process() - if(!port_servants()) - STOP_PROCESSING(SSfastprocess, src) - -/turf/open/indestructible/clock_spawn_room/proc/port_servants() - . = FALSE - for(var/mob/living/L in src) - if(is_servant_of_ratvar(L) && L.stat != DEAD) - . = TRUE - L.forceMove(get_turf(pick(GLOB.servant_spawns))) - visible_message("[L] vanishes in a flash of red!") - L.visible_message("[L] appears in a flash of red!", \ - "sas'so c'arta forbici
You're yanked away from [src]!") - playsound(src, 'sound/magic/enter_blood.ogg', 50, TRUE) - playsound(L, 'sound/magic/exit_blood.ogg', 50, TRUE) - flash_color(L, flash_color = "#C80000", flash_time = 10) - -/turf/open/Initalize_Atmos(times_fired) - excited = 0 - update_visuals() - - current_cycle = times_fired - - //cache some vars - var/list/atmos_adjacent_turfs = src.atmos_adjacent_turfs - - for(var/direction in GLOB.cardinals) - var/turf/open/enemy_tile = get_step(src, direction) - if(!istype(enemy_tile)) - if (atmos_adjacent_turfs) - atmos_adjacent_turfs -= enemy_tile - continue - var/datum/gas_mixture/enemy_air = enemy_tile.return_air() - - //only check this turf, if it didn't check us when it was initalized - if(enemy_tile.current_cycle < times_fired) - if(CANATMOSPASS(src, enemy_tile)) - LAZYINITLIST(atmos_adjacent_turfs) - LAZYINITLIST(enemy_tile.atmos_adjacent_turfs) - atmos_adjacent_turfs[enemy_tile] = TRUE - enemy_tile.atmos_adjacent_turfs[src] = TRUE - else - if (atmos_adjacent_turfs) - atmos_adjacent_turfs -= enemy_tile - if (enemy_tile.atmos_adjacent_turfs) - enemy_tile.atmos_adjacent_turfs -= src - UNSETEMPTY(enemy_tile.atmos_adjacent_turfs) - continue - else - if (!atmos_adjacent_turfs || !atmos_adjacent_turfs[enemy_tile]) - continue - - if(!excited && air.compare(enemy_air)) - //testing("Active turf found. Return value of compare(): [is_active]") - excited = TRUE - SSair.active_turfs |= src - UNSETEMPTY(atmos_adjacent_turfs) - if (atmos_adjacent_turfs) - src.atmos_adjacent_turfs = atmos_adjacent_turfs - -/turf/open/proc/GetHeatCapacity() - . = air.heat_capacity() - -/turf/open/proc/GetTemperature() - . = air.temperature - -/turf/open/proc/TakeTemperature(temp) - air.temperature += temp - air_update_turf() - -/turf/open/proc/freon_gas_act() - for(var/obj/I in contents) - if(I.resistance_flags & FREEZE_PROOF) - return - if(!(I.obj_flags & FROZEN)) - I.make_frozen_visual() - for(var/mob/living/L in contents) - if(L.bodytemperature <= 50) - L.apply_status_effect(/datum/status_effect/freon) - MakeSlippery(TURF_WET_PERMAFROST, 50) - return 1 - -/turf/open/proc/water_vapor_gas_act() - MakeSlippery(TURF_WET_WATER, min_wet_time = 100, wet_time_to_add = 50) - - for(var/mob/living/simple_animal/slime/M in src) - M.apply_water() - - SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) - for(var/obj/effect/O in src) - if(is_cleanable(O)) - qdel(O) - return TRUE - -/turf/open/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube) - if(C.movement_type & FLYING) - return 0 - if(has_gravity(src)) - var/obj/buckled_obj - if(C.buckled) - buckled_obj = C.buckled - if(!(lube&GALOSHES_DONT_HELP)) //can't slip while buckled unless it's lube. - return 0 - else - if(C.lying || !(C.status_flags & CANKNOCKDOWN)) // can't slip unbuckled mob if they're lying or can't fall. - return 0 - if(C.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING)) - return 0 - if(ishuman(C) && (lube&NO_SLIP_WHEN_WALKING)) - var/mob/living/carbon/human/H = C - if(!H.sprinting && H.getStaminaLoss() >= 20) - return 0 - if(!(lube&SLIDE_ICE)) - to_chat(C, "You slipped[ O ? " on the [O.name]" : ""]!") - playsound(C.loc, 'sound/misc/slip.ogg', 50, 1, -3) - - SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "slipped", /datum/mood_event/slipped) - for(var/obj/item/I in C.held_items) - C.accident(I) - - var/olddir = C.dir - if(!(lube & SLIDE_ICE)) - C.Knockdown(knockdown_amount) - C.stop_pulling() - else - C.Stun(20) - - if(buckled_obj) - buckled_obj.unbuckle_mob(C) - lube |= SLIDE_ICE - - if(lube&SLIDE) - new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 4), 1, FALSE, CALLBACK(C, /mob/living/carbon/.proc/spin, 1, 1)) - else if(lube&SLIDE_ICE) - new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 1), 1, FALSE) //spinning would be bad for ice, fucks up the next dir - return 1 - -/turf/open/proc/MakeSlippery(wet_setting = TURF_WET_WATER, min_wet_time = 0, wet_time_to_add = 0, max_wet_time = MAXIMUM_WET_TIME, permanent) - AddComponent(/datum/component/wet_floor, wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) - -/turf/open/proc/MakeDry(wet_setting = TURF_WET_WATER, immediate = FALSE, amount = INFINITY) - SEND_SIGNAL(src, COMSIG_TURF_MAKE_DRY, wet_setting, immediate, amount) - -/turf/open/get_dumping_location() - return src - -/turf/open/proc/ClearWet()//Nuclear option of immediately removing slipperyness from the tile instead of the natural drying over time - qdel(GetComponent(/datum/component/wet_floor)) - -/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]*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) - air.garbage_collect() +/turf/open + plane = FLOOR_PLANE + var/slowdown = 0 //negative for faster, positive for slower + + var/postdig_icon_change = FALSE + var/postdig_icon + var/wet + + var/footstep = null + +/turf/open/ComponentInitialize() + . = ..() + if(wet) + AddComponent(/datum/component/wet_floor, wet, INFINITY, 0, INFINITY, TRUE) + +/turf/open/MouseDrop_T(atom/dropping, mob/user) + . = ..() + if(dropping == user && isliving(user)) + var/mob/living/L = user + if(L.resting && do_after(L, max(10, L.getStaminaLoss()*0.5), 0, src)) + if(Adjacent(L, src)) + step(L, get_dir(L, src)) + playsound(L, "rustle", 25, 1) + +/turf/open/indestructible + name = "floor" + icon = 'icons/turf/floors.dmi' + icon_state = "floor" + footstep = FOOTSTEP_FLOOR + tiled_dirt = TRUE + +/turf/open/indestructible/Melt() + to_be_destroyed = FALSE + return src + +/turf/open/indestructible/singularity_act() + return + +/turf/open/indestructible/TerraformTurf(path, defer_change = FALSE, ignore_air = FALSE) + return + +/turf/open/indestructible/sound + name = "squeaky floor" + footstep = null + var/sound + +/turf/open/indestructible/sound/Entered(var/mob/AM) + ..() + if(istype(AM)) + playsound(src,sound,50,1) + +/turf/open/indestructible/cobble/side + icon_state = "cobble_side" + +/turf/open/indestructible/cobble/corner + icon_state = "cobble_corner" + +/turf/open/indestructible/cobble + name = "cobblestone path" + desc = "A simple but beautiful path made of various sized stones." + icon = 'icons/turf/floors.dmi' + icon_state = "cobble" + baseturfs = /turf/open/indestructible/cobble + tiled_dirt = FALSE + +/turf/open/indestructible/necropolis + name = "necropolis floor" + desc = "It's regarding you suspiciously." + icon = 'icons/turf/floors.dmi' + icon_state = "necro1" + baseturfs = /turf/open/indestructible/necropolis + initial_gas_mix = LAVALAND_DEFAULT_ATMOS + footstep = FOOTSTEP_LAVA + tiled_dirt = FALSE + +/turf/open/indestructible/necropolis/Initialize() + . = ..() + if(prob(12)) + icon_state = "necro[rand(2,3)]" + +/turf/open/indestructible/necropolis/air + initial_gas_mix = "o2=22;n2=82;TEMP=293.15" + +/turf/open/indestructible/boss //you put stone tiles on this and use it as a base + name = "necropolis floor" + icon = 'icons/turf/boss_floors.dmi' + icon_state = "boss" + baseturfs = /turf/open/indestructible/boss + initial_gas_mix = LAVALAND_DEFAULT_ATMOS + +/turf/open/indestructible/boss/air + initial_gas_mix = "o2=22;n2=82;TEMP=293.15" + +/turf/open/indestructible/hierophant + icon = 'icons/turf/floors/hierophant_floor.dmi' + initial_gas_mix = LAVALAND_DEFAULT_ATMOS + baseturfs = /turf/open/indestructible/hierophant + smooth = SMOOTH_TRUE + tiled_dirt = FALSE + +/turf/open/indestructible/hierophant/two + +/turf/open/indestructible/hierophant/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) + return FALSE + +/turf/open/indestructible/paper + name = "notebook floor" + desc = "A floor made of invulnerable notebook paper." + icon_state = "paperfloor" + footstep = null + tiled_dirt = FALSE + +/turf/open/indestructible/binary + name = "tear in the fabric of reality" + CanAtmosPass = ATMOS_PASS_NO + baseturfs = /turf/open/indestructible/binary + icon_state = "binary" + footstep = null + +/turf/open/indestructible/airblock + icon_state = "bluespace" + CanAtmosPass = ATMOS_PASS_NO + baseturfs = /turf/open/indestructible/airblock + +/turf/open/indestructible/clock_spawn_room + name = "cogmetal floor" + desc = "Brass plating that gently radiates heat. For some reason, it reminds you of blood." + icon_state = "reebe" + baseturfs = /turf/open/indestructible/clock_spawn_room + footstep = FOOTSTEP_PLATING + +/turf/open/indestructible/clock_spawn_room/Entered() + ..() + START_PROCESSING(SSfastprocess, src) + +/turf/open/indestructible/clock_spawn_room/Destroy() + STOP_PROCESSING(SSfastprocess, src) + . = ..() + +/turf/open/indestructible/clock_spawn_room/process() + if(!port_servants()) + STOP_PROCESSING(SSfastprocess, src) + +/turf/open/indestructible/clock_spawn_room/proc/port_servants() + . = FALSE + for(var/mob/living/L in src) + if(is_servant_of_ratvar(L) && L.stat != DEAD) + . = TRUE + L.forceMove(get_turf(pick(GLOB.servant_spawns))) + visible_message("[L] vanishes in a flash of red!") + L.visible_message("[L] appears in a flash of red!", \ + "sas'so c'arta forbici
You're yanked away from [src]!") + playsound(src, 'sound/magic/enter_blood.ogg', 50, TRUE) + playsound(L, 'sound/magic/exit_blood.ogg', 50, TRUE) + flash_color(L, flash_color = "#C80000", flash_time = 10) + +/turf/open/Initalize_Atmos(times_fired) + excited = 0 + update_visuals() + + current_cycle = times_fired + + //cache some vars + var/list/atmos_adjacent_turfs = src.atmos_adjacent_turfs + + for(var/direction in GLOB.cardinals) + var/turf/open/enemy_tile = get_step(src, direction) + if(!istype(enemy_tile)) + if (atmos_adjacent_turfs) + atmos_adjacent_turfs -= enemy_tile + continue + var/datum/gas_mixture/enemy_air = enemy_tile.return_air() + + //only check this turf, if it didn't check us when it was initalized + if(enemy_tile.current_cycle < times_fired) + if(CANATMOSPASS(src, enemy_tile)) + LAZYINITLIST(atmos_adjacent_turfs) + LAZYINITLIST(enemy_tile.atmos_adjacent_turfs) + atmos_adjacent_turfs[enemy_tile] = TRUE + enemy_tile.atmos_adjacent_turfs[src] = TRUE + else + if (atmos_adjacent_turfs) + atmos_adjacent_turfs -= enemy_tile + if (enemy_tile.atmos_adjacent_turfs) + enemy_tile.atmos_adjacent_turfs -= src + UNSETEMPTY(enemy_tile.atmos_adjacent_turfs) + continue + else + if (!atmos_adjacent_turfs || !atmos_adjacent_turfs[enemy_tile]) + continue + + if(!excited && air.compare(enemy_air)) + //testing("Active turf found. Return value of compare(): [is_active]") + excited = TRUE + SSair.active_turfs |= src + UNSETEMPTY(atmos_adjacent_turfs) + if (atmos_adjacent_turfs) + src.atmos_adjacent_turfs = atmos_adjacent_turfs + +/turf/open/proc/GetHeatCapacity() + . = air.heat_capacity() + +/turf/open/proc/GetTemperature() + . = air.temperature + +/turf/open/proc/TakeTemperature(temp) + air.temperature += temp + air_update_turf() + +/turf/open/proc/freon_gas_act() + for(var/obj/I in contents) + if(I.resistance_flags & FREEZE_PROOF) + return + if(!(I.obj_flags & FROZEN)) + I.make_frozen_visual() + for(var/mob/living/L in contents) + if(L.bodytemperature <= 50) + L.apply_status_effect(/datum/status_effect/freon) + MakeSlippery(TURF_WET_PERMAFROST, 50) + return 1 + +/turf/open/proc/water_vapor_gas_act() + MakeSlippery(TURF_WET_WATER, min_wet_time = 100, wet_time_to_add = 50) + + for(var/mob/living/simple_animal/slime/M in src) + M.apply_water() + + SEND_SIGNAL(src, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) + for(var/obj/effect/O in src) + if(is_cleanable(O)) + qdel(O) + return TRUE + +/turf/open/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube) + if(C.movement_type & FLYING) + return 0 + if(has_gravity(src)) + var/obj/buckled_obj + if(C.buckled) + buckled_obj = C.buckled + if(!(lube&GALOSHES_DONT_HELP)) //can't slip while buckled unless it's lube. + return 0 + else + if(C.lying || !(C.status_flags & CANKNOCKDOWN)) // can't slip unbuckled mob if they're lying or can't fall. + return 0 + if(C.m_intent == MOVE_INTENT_WALK && (lube&NO_SLIP_WHEN_WALKING)) + return 0 + if(ishuman(C) && (lube&NO_SLIP_WHEN_WALKING)) + var/mob/living/carbon/human/H = C + if(!H.sprinting && H.getStaminaLoss() >= 20) + return 0 + if(!(lube&SLIDE_ICE)) + to_chat(C, "You slipped[ O ? " on the [O.name]" : ""]!") + playsound(C.loc, 'sound/misc/slip.ogg', 50, 1, -3) + + SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "slipped", /datum/mood_event/slipped) + for(var/obj/item/I in C.held_items) + C.accident(I) + + var/olddir = C.dir + if(!(lube & SLIDE_ICE)) + C.Knockdown(knockdown_amount) + C.stop_pulling() + else + C.Stun(20) + + if(buckled_obj) + buckled_obj.unbuckle_mob(C) + lube |= SLIDE_ICE + + if(lube&SLIDE) + new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 4), 1, FALSE, CALLBACK(C, /mob/living/carbon/.proc/spin, 1, 1)) + else if(lube&SLIDE_ICE) + new /datum/forced_movement(C, get_ranged_target_turf(C, olddir, 1), 1, FALSE) //spinning would be bad for ice, fucks up the next dir + return 1 + +/turf/open/proc/MakeSlippery(wet_setting = TURF_WET_WATER, min_wet_time = 0, wet_time_to_add = 0, max_wet_time = MAXIMUM_WET_TIME, permanent) + AddComponent(/datum/component/wet_floor, wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) + +/turf/open/proc/MakeDry(wet_setting = TURF_WET_WATER, immediate = FALSE, amount = INFINITY) + SEND_SIGNAL(src, COMSIG_TURF_MAKE_DRY, wet_setting, immediate, amount) + +/turf/open/get_dumping_location() + return src + +/turf/open/proc/ClearWet()//Nuclear option of immediately removing slipperyness from the tile instead of the natural drying over time + qdel(GetComponent(/datum/component/wet_floor)) + +/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]*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/assembly/bomb.dm b/code/modules/assembly/bomb.dm index 75a0492785..8ec93f978f 100644 --- a/code/modules/assembly/bomb.dm +++ b/code/modules/assembly/bomb.dm @@ -1,202 +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 - var/fuel_moles = air_contents.gases[/datum/gas/plasma] + air_contents.gases[/datum/gas/oxygen]/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/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index 570b62a081..a112eabbf7 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -5,8 +5,6 @@ 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 /datum/gas_mixture var/list/gases = list() @@ -22,23 +20,6 @@ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm if (!isnull(volume)) src.volume = volume -//listmos procs -//use the macros in performance intensive areas. for their definitions, refer to code/__DEFINES/atmospherics.dm - -//UNOMOS - whoever originally wrote this is a sadist that just wants to see byond suffer. - - //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. - //UNOMOS - i have no idea exactly what the fuck or how the fuck it's the case, but removing this proc can and will completely nullify all of the performance gain from removing add_gas and assert_gas. so uh, dont remove it i guess. Why this shit isn't a define is beyond me. -/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]) <= 0) - cached_gases -= id - //PV = nRT /datum/gas_mixture/proc/heat_capacity() //joules per kelvin @@ -152,7 +133,7 @@ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm for(var/id in cached_gases) removed_gases[id] = QUANTIZE((cached_gases[id] / sum) * amount) cached_gases[id] -= removed_gases[id] - garbage_collect() + GAS_GARBAGE_COLLECT(gases) return removed @@ -170,7 +151,7 @@ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm removed_gases[id] = QUANTIZE(cached_gases[id] * ratio) cached_gases[id] -= removed_gases[id] - garbage_collect() + GAS_GARBAGE_COLLECT(gases) return removed @@ -282,11 +263,8 @@ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm 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) @@ -391,7 +369,7 @@ GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm 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/immutable_mixtures.dm b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm index 77a00841cf..db6bc45f2b 100644 --- a/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm +++ b/code/modules/atmospherics/gasmixtures/immutable_mixtures.dm @@ -7,9 +7,6 @@ /datum/gas_mixture/immutable/New() ..() - garbage_collect() - -/datum/gas_mixture/immutable/garbage_collect() temperature = initial_temperature temperature_archived = initial_temperature gases.Cut() @@ -19,7 +16,9 @@ /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. @@ -59,9 +58,13 @@ /datum/gas_mixture/immutable/cloner initial_temperature = T20C -/datum/gas_mixture/immutable/cloner/garbage_collect() +/datum/gas_mixture/immutable/cloner/New() ..() gases[/datum/gas/nitrogen] = MOLES_O2STANDARD + MOLES_N2STANDARD +/datum/gas_mixture/immutable/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/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index 5f4e79174b..f065882a8d 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" = GLOB.meta_gas_info[gas_id][META_GAS_NAME], - "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_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] * 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_info[gas_id][META_GAS_NAME], + "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_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] * 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 b834d65084..f045c2a10d 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -1,237 +1,237 @@ -/obj/machinery/atmospherics/components/trinary/filter - name = "gas filter" - icon_state = "filter_off" - desc = "Very useful for filtering gasses." - density = FALSE - can_unwrench = TRUE - var/target_pressure = ONE_ATMOSPHERE - var/filter_type = null - var/frequency = 0 - var/datum/radio_frequency/radio_connection - - construction_type = /obj/item/pipe/trinary/flippable - pipe_state = "filter" - -/obj/machinery/atmospherics/components/trinary/filter/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/trinary/filter/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/trinary/filter/flipped - icon_state = "filter_off_f" - flipped = TRUE - -/obj/machinery/atmospherics/components/trinary/filter/flipped/layer1 - piping_layer = PIPING_LAYER_MIN - pixel_x = -PIPING_LAYER_P_X - pixel_y = -PIPING_LAYER_P_Y - -/obj/machinery/atmospherics/components/trinary/filter/flipped/layer3 - piping_layer = PIPING_LAYER_MAX - pixel_x = PIPING_LAYER_P_X - pixel_y = PIPING_LAYER_P_Y - -// These two filter types have critical_machine flagged to on and thus causes the area they are in to be exempt from the Grid Check event. - -/obj/machinery/atmospherics/components/trinary/filter/critical - critical_machine = TRUE - -/obj/machinery/atmospherics/components/trinary/filter/flipped/critical - critical_machine = TRUE - -/obj/machinery/atmospherics/components/trinary/filter/proc/set_frequency(new_frequency) - SSradio.remove_object(src, frequency) - frequency = new_frequency - if(frequency) - radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) - -/obj/machinery/atmospherics/components/trinary/filter/Destroy() - SSradio.remove_object(src,frequency) - return ..() - -/obj/machinery/atmospherics/components/trinary/filter/atmos //Used for atmos waste loops - on = TRUE - icon_state = "filter_on" - -/obj/machinery/atmospherics/components/trinary/filter/atmos/n2 - name = "nitrogen filter" - filter_type = "n2" -/obj/machinery/atmospherics/components/trinary/filter/atmos/o2 - name = "oxygen filter" - filter_type = "o2" -/obj/machinery/atmospherics/components/trinary/filter/atmos/co2 - name = "carbon dioxide filter" - filter_type = "co2" -/obj/machinery/atmospherics/components/trinary/filter/atmos/n2o - name = "nitrous oxide filter" - filter_type = "n2o" -/obj/machinery/atmospherics/components/trinary/filter/atmos/plasma - name = "plasma filter" - filter_type = "plasma" - -/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped //This feels wrong, I know - icon_state = "filter_on_f" - flipped = TRUE - -/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/n2 - name = "nitrogen filter" - filter_type = "n2" -/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/o2 - name = "oxygen filter" - filter_type = "o2" -/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/co2 - name = "carbon dioxide filter" - filter_type = "co2" -/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/n2o - name = "nitrous oxide filter" - filter_type = "n2o" -/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/plasma - name = "plasma filter" - filter_type = "plasma" - -/obj/machinery/atmospherics/components/trinary/filter/update_icon() - cut_overlays() - for(var/direction in GLOB.cardinals) - if(direction & initialize_directions) - var/obj/machinery/atmospherics/node = findConnecting(direction) - if(node) - add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction, node.pipe_color)) - continue - add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction)) - ..() - -/obj/machinery/atmospherics/components/trinary/filter/update_icon_nopipes() - if(on && nodes[1] && nodes[2] && nodes[3] && is_operational()) - icon_state = "filter_on[flipped?"_f":""]" - return - icon_state = "filter_off[flipped?"_f":""]" - -/obj/machinery/atmospherics/components/trinary/filter/power_change() - var/old_stat = stat - ..() - if(stat != old_stat) - update_icon() - -/obj/machinery/atmospherics/components/trinary/filter/process_atmos() - ..() - if(!on || !(nodes[1] && nodes[2] && nodes[3]) || !is_operational()) - return - - var/datum/gas_mixture/air1 = airs[1] - var/datum/gas_mixture/air2 = airs[2] - var/datum/gas_mixture/air3 = airs[3] - - var/output_starting_pressure = air3.return_pressure() - - if(output_starting_pressure >= target_pressure) - //No need to transfer if target is already full! - return - - //Calculate necessary moles to transfer using PV=nRT - - var/pressure_delta = target_pressure - output_starting_pressure - var/transfer_moles - - if(air1.temperature > 0) - transfer_moles = pressure_delta*air3.volume/(air1.temperature * R_IDEAL_GAS_EQUATION) - - //Actually transfer the gas - - if(transfer_moles > 0) - var/datum/gas_mixture/removed = air1.remove(transfer_moles) - - if(!removed) - return - - var/filtering = TRUE - if(!ispath(filter_type)) - if(filter_type) - filter_type = gas_id2path(filter_type) //support for mappers so they don't need to type out paths - else - filtering = FALSE - - if(filtering && removed.gases[filter_type]) - var/datum/gas_mixture/filtered_out = new - - filtered_out.temperature = removed.temperature - filtered_out.gases[filter_type] = removed.gases[filter_type] - - removed.gases[filter_type] = 0 - removed.garbage_collect() - - 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) - - air3.merge(removed) - - update_parents() - -/obj/machinery/atmospherics/components/trinary/filter/atmosinit() - set_frequency(frequency) - return ..() - -/obj/machinery/atmospherics/components/trinary/filter/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 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, "atmos_filter", name, 475, 195, master_ui, state) - ui.open() - -/obj/machinery/atmospherics/components/trinary/filter/ui_data() - var/data = list() - data["on"] = on - data["pressure"] = round(target_pressure) - data["max_pressure"] = round(MAX_OUTPUT_PRESSURE) - - data["filter_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)))) - - return data - -/obj/machinery/atmospherics/components/trinary/filter/ui_act(action, params) - if(..()) - return - switch(action) - if("power") - on = !on - investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS) - . = TRUE - if("pressure") - var/pressure = params["pressure"] - if(pressure == "max") - pressure = MAX_OUTPUT_PRESSURE - . = TRUE - else if(pressure == "input") - pressure = input("New output pressure (0-[MAX_OUTPUT_PRESSURE] kPa):", name, target_pressure) as num|null - if(!isnull(pressure) && !..()) - . = TRUE - else if(text2num(pressure) != null) - pressure = text2num(pressure) - . = TRUE - if(.) - target_pressure = CLAMP(pressure, 0, MAX_OUTPUT_PRESSURE) - investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", INVESTIGATE_ATMOS) - if("filter") - filter_type = null - var/filter_name = "nothing" - var/gas = gas_id2path(params["mode"]) - if(gas in GLOB.meta_gas_info) - filter_type = gas - filter_name = GLOB.meta_gas_info[gas][META_GAS_NAME] - investigate_log("was set to filter [filter_name] by [key_name(usr)]", INVESTIGATE_ATMOS) - . = TRUE - update_icon() - -/obj/machinery/atmospherics/components/trinary/filter/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/trinary/filter + name = "gas filter" + icon_state = "filter_off" + desc = "Very useful for filtering gasses." + density = FALSE + can_unwrench = TRUE + var/target_pressure = ONE_ATMOSPHERE + var/filter_type = null + var/frequency = 0 + var/datum/radio_frequency/radio_connection + + construction_type = /obj/item/pipe/trinary/flippable + pipe_state = "filter" + +/obj/machinery/atmospherics/components/trinary/filter/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/trinary/filter/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/trinary/filter/flipped + icon_state = "filter_off_f" + flipped = TRUE + +/obj/machinery/atmospherics/components/trinary/filter/flipped/layer1 + piping_layer = PIPING_LAYER_MIN + pixel_x = -PIPING_LAYER_P_X + pixel_y = -PIPING_LAYER_P_Y + +/obj/machinery/atmospherics/components/trinary/filter/flipped/layer3 + piping_layer = PIPING_LAYER_MAX + pixel_x = PIPING_LAYER_P_X + pixel_y = PIPING_LAYER_P_Y + +// These two filter types have critical_machine flagged to on and thus causes the area they are in to be exempt from the Grid Check event. + +/obj/machinery/atmospherics/components/trinary/filter/critical + critical_machine = TRUE + +/obj/machinery/atmospherics/components/trinary/filter/flipped/critical + critical_machine = TRUE + +/obj/machinery/atmospherics/components/trinary/filter/proc/set_frequency(new_frequency) + SSradio.remove_object(src, frequency) + frequency = new_frequency + if(frequency) + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) + +/obj/machinery/atmospherics/components/trinary/filter/Destroy() + SSradio.remove_object(src,frequency) + return ..() + +/obj/machinery/atmospherics/components/trinary/filter/atmos //Used for atmos waste loops + on = TRUE + icon_state = "filter_on" + +/obj/machinery/atmospherics/components/trinary/filter/atmos/n2 + name = "nitrogen filter" + filter_type = "n2" +/obj/machinery/atmospherics/components/trinary/filter/atmos/o2 + name = "oxygen filter" + filter_type = "o2" +/obj/machinery/atmospherics/components/trinary/filter/atmos/co2 + name = "carbon dioxide filter" + filter_type = "co2" +/obj/machinery/atmospherics/components/trinary/filter/atmos/n2o + name = "nitrous oxide filter" + filter_type = "n2o" +/obj/machinery/atmospherics/components/trinary/filter/atmos/plasma + name = "plasma filter" + filter_type = "plasma" + +/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped //This feels wrong, I know + icon_state = "filter_on_f" + flipped = TRUE + +/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/n2 + name = "nitrogen filter" + filter_type = "n2" +/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/o2 + name = "oxygen filter" + filter_type = "o2" +/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/co2 + name = "carbon dioxide filter" + filter_type = "co2" +/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/n2o + name = "nitrous oxide filter" + filter_type = "n2o" +/obj/machinery/atmospherics/components/trinary/filter/atmos/flipped/plasma + name = "plasma filter" + filter_type = "plasma" + +/obj/machinery/atmospherics/components/trinary/filter/update_icon() + cut_overlays() + for(var/direction in GLOB.cardinals) + if(direction & initialize_directions) + var/obj/machinery/atmospherics/node = findConnecting(direction) + if(node) + add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction, node.pipe_color)) + continue + add_overlay(getpipeimage('icons/obj/atmospherics/components/trinary_devices.dmi', "cap", direction)) + ..() + +/obj/machinery/atmospherics/components/trinary/filter/update_icon_nopipes() + if(on && nodes[1] && nodes[2] && nodes[3] && is_operational()) + icon_state = "filter_on[flipped?"_f":""]" + return + icon_state = "filter_off[flipped?"_f":""]" + +/obj/machinery/atmospherics/components/trinary/filter/power_change() + var/old_stat = stat + ..() + if(stat != old_stat) + update_icon() + +/obj/machinery/atmospherics/components/trinary/filter/process_atmos() + ..() + if(!on || !(nodes[1] && nodes[2] && nodes[3]) || !is_operational()) + return + + var/datum/gas_mixture/air1 = airs[1] + var/datum/gas_mixture/air2 = airs[2] + var/datum/gas_mixture/air3 = airs[3] + + var/output_starting_pressure = air3.return_pressure() + + if(output_starting_pressure >= target_pressure) + //No need to transfer if target is already full! + return + + //Calculate necessary moles to transfer using PV=nRT + + var/pressure_delta = target_pressure - output_starting_pressure + var/transfer_moles + + if(air1.temperature > 0) + transfer_moles = pressure_delta*air3.volume/(air1.temperature * R_IDEAL_GAS_EQUATION) + + //Actually transfer the gas + + if(transfer_moles > 0) + var/datum/gas_mixture/removed = air1.remove(transfer_moles) + + if(!removed) + return + + var/filtering = TRUE + if(!ispath(filter_type)) + if(filter_type) + filter_type = gas_id2path(filter_type) //support for mappers so they don't need to type out paths + else + filtering = FALSE + + if(filtering && removed.gases[filter_type]) + var/datum/gas_mixture/filtered_out = new + + filtered_out.temperature = removed.temperature + filtered_out.gases[filter_type] = removed.gases[filter_type] + + 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) + + air3.merge(removed) + + update_parents() + +/obj/machinery/atmospherics/components/trinary/filter/atmosinit() + set_frequency(frequency) + return ..() + +/obj/machinery/atmospherics/components/trinary/filter/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 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, "atmos_filter", name, 475, 195, master_ui, state) + ui.open() + +/obj/machinery/atmospherics/components/trinary/filter/ui_data() + var/data = list() + data["on"] = on + data["pressure"] = round(target_pressure) + data["max_pressure"] = round(MAX_OUTPUT_PRESSURE) + + data["filter_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)))) + + return data + +/obj/machinery/atmospherics/components/trinary/filter/ui_act(action, params) + if(..()) + return + switch(action) + if("power") + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(usr)]", INVESTIGATE_ATMOS) + . = TRUE + if("pressure") + var/pressure = params["pressure"] + if(pressure == "max") + pressure = MAX_OUTPUT_PRESSURE + . = TRUE + else if(pressure == "input") + pressure = input("New output pressure (0-[MAX_OUTPUT_PRESSURE] kPa):", name, target_pressure) as num|null + if(!isnull(pressure) && !..()) + . = TRUE + else if(text2num(pressure) != null) + pressure = text2num(pressure) + . = TRUE + if(.) + target_pressure = CLAMP(pressure, 0, MAX_OUTPUT_PRESSURE) + investigate_log("was set to [target_pressure] kPa by [key_name(usr)]", INVESTIGATE_ATMOS) + if("filter") + filter_type = null + var/filter_name = "nothing" + var/gas = gas_id2path(params["mode"]) + if(gas in GLOB.meta_gas_info) + filter_type = gas + filter_name = GLOB.meta_gas_info[gas][META_GAS_NAME] + investigate_log("was set to filter [filter_name] by [key_name(usr)]", INVESTIGATE_ATMOS) + . = TRUE + update_icon() + +/obj/machinery/atmospherics/components/trinary/filter/can_unwrench(mob/user) + . = ..() + if(. && on && is_operational()) + to_chat(user, "You cannot unwrench [src], turn it off first!") + return FALSE diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 8729f30fb0..bfe60cd573 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -184,7 +184,7 @@ beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. beaker.reagents.reaction(occupant, VAPOR) air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this - air1.garbage_collect() + 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 @@ -221,7 +221,7 @@ mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) 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. - air1.garbage_collect() + GAS_GARBAGE_COLLECT(air1.gases) /obj/machinery/atmospherics/components/unary/cryo_cell/power_change() ..() 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 02bde682a9..cd69f5b8c6 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_scrubber.dm @@ -1,329 +1,329 @@ -#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_gases[gas] = removed_gases[gas] - removed_gases[gas] = 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_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_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/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm index eed7afaaa4..44ae465982 100644 --- a/code/modules/atmospherics/machinery/portable/scrubber.dm +++ b/code/modules/atmospherics/machinery/portable/scrubber.dm @@ -1,145 +1,145 @@ -/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 - 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_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 ..() diff --git a/code/modules/events/spacevine.dm b/code/modules/events/spacevine.dm index 53f3832c7a..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] = max(GM.gases[/datum/gas/oxygen] - 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] = max(GM.gases[/datum/gas/nitrogen] - 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] = max(GM.gases[/datum/gas/carbon_dioxide] - 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] = max(GM.gases[/datum/gas/plasma] - 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/integrated_electronics/subtypes/atmospherics.dm b/code/modules/integrated_electronics/subtypes/atmospherics.dm index d9408ca466..7f1705e151 100644 --- a/code/modules/integrated_electronics/subtypes/atmospherics.dm +++ b/code/modules/integrated_electronics/subtypes/atmospherics.dm @@ -1,761 +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(GLOB.meta_gas_info[filtered_gas][META_GAS_NAME] 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 - 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_info[filtered_gas][META_GAS_NAME] 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/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index ae5cee48b6..b8edd34ee9 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -31,7 +31,7 @@ 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/life.dm b/code/modules/mob/living/carbon/life.dm index b6cb5862d6..4acfc25d68 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -291,7 +291,7 @@ - breath.garbage_collect() + GAS_GARBAGE_COLLECT(breath.gases) //BREATH TEMPERATURE handle_breath_temperature(breath) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 0f09773bc3..2517d2438d 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1,588 +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 - - 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] - - 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/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm index 3c86aba3b2..4159d9898a 100644 --- a/code/modules/power/singularity/collector.dm +++ b/code/modules/power/singularity/collector.dm @@ -46,7 +46,7 @@ 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 - loaded_tank.air_contents.garbage_collect() + GAS_GARBAGE_COLLECT(loaded_tank.air_contents.gases) var/power_produced = RAD_COLLECTOR_OUTPUT add_avail(power_produced) @@ -60,7 +60,7 @@ 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 - loaded_tank.air_contents.garbage_collect() + 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 diff --git a/code/modules/research/xenobiology/crossbreeding/chilling.dm b/code/modules/research/xenobiology/crossbreeding/chilling.dm index c15e31267a..cfbbc8e4a1 100644 --- a/code/modules/research/xenobiology/crossbreeding/chilling.dm +++ b/code/modules/research/xenobiology/crossbreeding/chilling.dm @@ -103,7 +103,7 @@ Chilling extracts: if(istype(G)) 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 49b822d916..9a010e881c 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -339,7 +339,7 @@ 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 959103819e..511cd9abfb 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,389 +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 - - 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 - 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]/total_moles - to_chat(user, "[GLOB.meta_gas_info[id][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_info[id][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()