#define REAGENT_SPILL_DIVISOR 200 /obj/structure/reagent_dispensers name = "Dispenser" desc = "..." icon = 'icons/obj/medical/chemical_tanks.dmi' icon_state = "water" density = TRUE anchored = FALSE pressure_resistance = 2*ONE_ATMOSPHERE max_integrity = 300 /// In units, how much the dispenser can hold var/tank_volume = 1000 /// The ID of the reagent that the dispenser uses var/reagent_id = /datum/reagent/water /// Can you turn this into a plumbing tank? var/can_be_tanked = TRUE /// Is this source self-replenishing? var/refilling = FALSE /// Can this dispenser be opened using a wrench? var/openable = FALSE /// Is this dispenser slowly leaking its reagent? var/leaking = FALSE /// How much reagent to leak var/amount_to_leak = 10 /// An assembly attached to the tank - if this dispenser accepts_rig var/obj/item/assembly_holder/rig = null /// Whether this dispenser can be rigged with an assembly (and blown up with an igniter) var/accepts_rig = FALSE //overlay of attached assemblies var/mutable_appearance/assembliesoverlay /// The person who attached an assembly to this dispenser, for bomb logging purposes var/last_rigger = "" /// is it climbable? some of our wall-mounted dispensers should not have this var/climbable = FALSE // This check is necessary for assemblies to automatically detect that we are compatible /obj/structure/reagent_dispensers/IsSpecialAssembly() return accepts_rig /obj/structure/reagent_dispensers/Destroy() QDEL_NULL(rig) return ..() /** * rig_boom: Wrapper to log when a reagent_dispenser is set off by an assembly * */ /obj/structure/reagent_dispensers/proc/rig_boom() log_bomber(last_rigger, "rigged [src] exploded", src) boom() /obj/structure/reagent_dispensers/Initialize(mapload) . = ..() if(icon_state == "water" && check_holidays(APRIL_FOOLS)) icon_state = "water_fools" if(climbable) AddElement(/datum/element/climbable, climb_time = 4 SECONDS, climb_stun = 4 SECONDS) AddElement(/datum/element/elevation, pixel_shift = 14) /obj/structure/reagent_dispensers/examine(mob/user) . = ..() if(can_be_tanked) . += span_notice("Use a sheet of iron to convert this into a plumbing-compatible tank.") if(openable) if(!leaking) . += span_notice("Its tap looks like it could be wrenched open.") else . += span_warning("Its tap is wrenched open!") if(accepts_rig && get_dist(user, src) <= 2) if(rig) . += span_warning("There is some kind of device rigged to the tank!") else . += span_notice("It looks like you could rig a device to the tank.") /obj/structure/reagent_dispensers/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir) . = ..() if(. && atom_integrity > 0) if(tank_volume && (damage_flag == BULLET || damage_flag == LASER)) boom() /obj/structure/reagent_dispensers/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) if(attacking_item.is_refillable()) return FALSE //so we can refill them via their afterattack. if(istype(attacking_item, /obj/item/assembly_holder) && accepts_rig) if(rig) balloon_alert(user, "another device is in the way!") return ..() var/obj/item/assembly_holder/holder = attacking_item if(!(locate(/obj/item/assembly/igniter) in holder.assemblies)) return ..() user.balloon_alert_to_viewers("attaching rig...") add_fingerprint(user) if(!do_after(user, 2 SECONDS, target = src) || !user.transferItemToLoc(holder, src)) return rig = holder holder.master = src holder.on_attach() assembliesoverlay = holder assembliesoverlay.pixel_w += 6 assembliesoverlay.pixel_z += 1 add_overlay(assembliesoverlay) RegisterSignal(src, COMSIG_IGNITER_ACTIVATE, PROC_REF(rig_boom)) log_bomber(user, "attached [holder.name] to ", src) last_rigger = user user.balloon_alert_to_viewers("attached rig") return if(istype(attacking_item, /obj/item/stack/sheet/iron) && can_be_tanked) var/obj/item/stack/sheet/iron/metal_stack = attacking_item metal_stack.use(1) var/obj/structure/reagent_dispensers/plumbed/storage/new_tank = new /obj/structure/reagent_dispensers/plumbed/storage(drop_location()) new_tank.reagents.maximum_volume = reagents.maximum_volume reagents.trans_to(new_tank, reagents.total_volume) new_tank.name = "stationary [name]" new_tank.update_appearance(UPDATE_OVERLAYS) new_tank.set_anchored(anchored) qdel(src) return FALSE return ..() /obj/structure/reagent_dispensers/Exited(atom/movable/gone, direction) . = ..() if(gone == rig) rig = null /obj/structure/reagent_dispensers/attack_hand(mob/user, list/modifiers) . = ..() if(. || !rig) return // mousetrap rigs only make sense if you can set them off, can't step on them // If you see a mousetrap-rigged fuel tank, just leave it alone rig.on_found() if(QDELETED(src)) return user.balloon_alert_to_viewers("detaching rig...") if(!do_after(user, 2 SECONDS, target = src)) return user.balloon_alert_to_viewers("detached rig") user.log_message("detached [rig] from [src].", LOG_GAME) if(!user.put_in_hands(rig)) rig.forceMove(get_turf(user)) rig = null last_rigger = null cut_overlays(assembliesoverlay) UnregisterSignal(src, COMSIG_IGNITER_ACTIVATE) /obj/structure/reagent_dispensers/Initialize(mapload) create_reagents(tank_volume, DRAINABLE | AMOUNT_VISIBLE) if(reagent_id) reagents.add_reagent(reagent_id, tank_volume) . = ..() /** * boom: Detonate a reagent dispenser. * * This is most dangerous for fuel tanks, which will explosion(). * Other dispensers will scatter their contents within range. */ /obj/structure/reagent_dispensers/proc/boom(damage_type = BRUTE, guaranteed_violent = FALSE) //SKYRAT EDIT CHANGE if(QDELETED(src)) return // little bit of sanity sauce before we wreck ourselves somehow var/datum/reagent/fuel/volatiles = reagents.has_reagent(/datum/reagent/fuel) var/fuel_amt = 0 if(istype(volatiles) && volatiles.volume >= 25) fuel_amt = volatiles.volume reagents.del_reagent(/datum/reagent/fuel) // not actually used for the explosion if(reagents.total_volume) if(!fuel_amt) visible_message(span_danger("\The [src] ruptures!")) // Leave it up to future terrorists to figure out the best way to mix reagents with fuel for a useful boom here chem_splash(loc, null, 2 + (reagents.total_volume + fuel_amt) / 1000, list(reagents), extra_heat=(fuel_amt / 50),adminlog=(fuel_amt<25)) if(fuel_amt) // with that done, actually explode visible_message(span_danger("\The [src] explodes!")) // old code for reference: // standard fuel tank = 1000 units = heavy_impact_range = 1, light_impact_range = 5, flame_range = 5 // big fuel tank = 5000 units = devastation_range = 1, heavy_impact_range = 2, light_impact_range = 7, flame_range = 12 // It did not account for how much fuel was actually in the tank at all, just the size of the tank. // I encourage others to better scale these numbers in the future. // As it stands this is a minor nerf in exchange for an easy bombing technique working that has been broken for a while. switch(fuel_amt) if(25 to 150) explosion(src, light_impact_range = 1, flame_range = 2) if(150 to 300) explosion(src, light_impact_range = 2, flame_range = 3) if(300 to 750) explosion(src, heavy_impact_range = 1, light_impact_range = 3, flame_range = 5) if(750 to 1500) explosion(src, heavy_impact_range = 1, light_impact_range = 4, flame_range = 6) if(1500 to INFINITY) explosion(src, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 6, flame_range = 8) qdel(src) /obj/structure/reagent_dispensers/atom_deconstruct(disassembled = TRUE) if(!disassembled) boom() /obj/structure/reagent_dispensers/proc/tank_leak() if(leaking && reagents && reagents.total_volume >= amount_to_leak) reagents.expose(get_turf(src), TOUCH, amount_to_leak / max(amount_to_leak, reagents.total_volume)) reagents.remove_reagent(reagent_id, amount_to_leak) playsound(src, 'sound/effects/glug.ogg', 33, TRUE, SILENCED_SOUND_EXTRARANGE) return TRUE return FALSE /obj/structure/reagent_dispensers/proc/knock_down() var/datum/effect_system/fluid_spread/smoke/chem/smoke = new () var/range = reagents.total_volume / REAGENT_SPILL_DIVISOR smoke.attach(drop_location()) smoke.set_up(round(range), holder = drop_location(), location = drop_location(), carry = reagents, silent = FALSE) smoke.start(log = TRUE) reagents.clear_reagents() qdel(src) /obj/structure/reagent_dispensers/wrench_act(mob/living/user, obj/item/tool) . = ..() if(!openable) return FALSE leaking = !leaking balloon_alert(user, "[leaking ? "opened" : "closed"] tap") user.log_message("[leaking ? "opened" : "closed"] [src].", LOG_GAME) tank_leak() return ITEM_INTERACT_SUCCESS /obj/structure/reagent_dispensers/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) . = ..() tank_leak() /obj/structure/reagent_dispensers/watertank name = "water tank" desc = "A water tank." icon_state = "water" openable = TRUE climbable = TRUE /obj/structure/reagent_dispensers/watertank/high name = "high-capacity water tank" desc = "A highly pressurized water tank made to hold gargantuan amounts of water." icon_state = "water_high" //I was gonna clean my room... tank_volume = 3000 /obj/structure/reagent_dispensers/foamtank//SKYRAT EDIT - ICON OVERRIDDEN BY AESTHETICS - SEE MODULE name = "firefighting foam tank" desc = "A tank full of firefighting foam." icon_state = "foam" reagent_id = /datum/reagent/firefighting_foam tank_volume = 500 openable = TRUE climbable = TRUE /obj/structure/reagent_dispensers/fueltank//SKYRAT EDIT - ICON OVERRIDDEN BY AESTHETICS - SEE MODULE name = "fuel tank" desc = "A tank full of industrial welding fuel. Do not consume." icon_state = "fuel" reagent_id = /datum/reagent/fuel openable = TRUE accepts_rig = TRUE climbable = TRUE /obj/structure/reagent_dispensers/fueltank/Initialize(mapload) . = ..() if(check_holidays(APRIL_FOOLS)) icon_state = "fuel_fools" /obj/structure/reagent_dispensers/fueltank/boom(damage_type = BRUTE, guaranteed_violent = FALSE) //SKYRAT EDIT CHANGE if(damage_type == BURN || guaranteed_violent) explosion(src, heavy_impact_range = 1, light_impact_range = 5, flame_range = 5) qdel(src) else . = ..() //SKYRAT EDIT END /obj/structure/reagent_dispensers/fueltank/blob_act(obj/structure/blob/B) boom(guaranteed_violent = TRUE) //SKYRAT EDIT CHANGE /obj/structure/reagent_dispensers/fueltank/ex_act() boom(guaranteed_violent = TRUE) //SKYRAT EDIT CHANGE return TRUE /obj/structure/reagent_dispensers/fueltank/fire_act(exposed_temperature, exposed_volume) boom(guaranteed_violent = TRUE) //SKYRAT EDIT CHANGE /obj/structure/reagent_dispensers/fueltank/zap_act(power, zap_flags) . = ..() //extend the zap if(ZAP_OBJ_DAMAGE & zap_flags) boom(guaranteed_violent = TRUE) //SKYRAT EDIT CHANGE /obj/structure/reagent_dispensers/fueltank/bullet_act(obj/projectile/hitting_projectile) if(hitting_projectile.damage > 0 && ((hitting_projectile.damage_type == BURN) || (hitting_projectile.damage_type == BRUTE))) log_bomber(hitting_projectile.firer, "detonated a", src, "via projectile") boom(guaranteed_violent = TRUE) // SKYRAT EDIT CHANGE return hitting_projectile.on_hit(src, 0) // we override parent like this because otherwise we won't actually properly log the fact that a projectile caused this welding tank to explode. // if this sucks, feel free to change it, but make sure the damn thing will log. thanks. return ..() /obj/structure/reagent_dispensers/fueltank/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers) if(attacking_item.tool_behaviour != TOOL_WELDER) return ..() var/obj/item/weldingtool/refilling_welder = attacking_item if(istype(refilling_welder) && !refilling_welder.welding) if(refilling_welder.reagents.has_reagent(/datum/reagent/fuel, refilling_welder.max_fuel)) to_chat(user, span_warning("Your [refilling_welder.name] is already full!")) return reagents.trans_to(refilling_welder, refilling_welder.max_fuel, transferred_by = user) user.visible_message(span_notice("[user] refills [user.p_their()] [refilling_welder.name]."), span_notice("You refill [refilling_welder].")) playsound(src, 'sound/effects/refill.ogg', 50, TRUE) refilling_welder.update_appearance() return var/obj/item/lighter/refilling_lighter = attacking_item if(istype(refilling_lighter) && !refilling_lighter.lit) if(refilling_lighter.reagents.has_reagent(/datum/reagent/fuel, refilling_lighter.maximum_fuel)) to_chat(user, span_warning("Your [refilling_lighter.name] is already full!")) return reagents.trans_to(refilling_lighter, refilling_lighter.maximum_fuel, transferred_by = user) user.visible_message(span_notice("[user] refills [user.p_their()] [refilling_lighter.name]."), span_notice("You refill [refilling_lighter].")) playsound(src, 'sound/effects/refill.ogg', 25, TRUE) return if(!reagents.has_reagent(/datum/reagent/fuel)) to_chat(user, span_warning("[src] is out of fuel!")) return user.visible_message( span_danger("[user] catastrophically fails at refilling [user.p_their()] [attacking_item.name]!"), span_userdanger("That was stupid of you.")) log_bomber(user, "detonated a", src, "via [attacking_item.name]") boom() /obj/structure/reagent_dispensers/fueltank/large name = "high capacity fuel tank" desc = "A tank full of a high quantity of welding fuel. Keep away from open flames." icon_state = "fuel_high" tank_volume = 5000 /obj/structure/reagent_dispensers/fueltank/large/boom(damage_type = BRUTE, guaranteed_violent = FALSE) //SKYRAT EDIT CHANGE if(damage_type == BURN || guaranteed_violent) explosion(src, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 7, flame_range = 12) qdel(src) else . = ..() //SKYRAT EDIT END /// Wall mounted dispeners, like pepper spray or virus food. Not a normal tank, and shouldn't be able to be turned into a plumbed stationary one. /obj/structure/reagent_dispensers/wall anchored = TRUE density = FALSE can_be_tanked = FALSE /obj/structure/reagent_dispensers/wall/peppertank name = "pepper spray refiller" desc = "Contains condensed capsaicin for use in law \"enforcement.\"" icon_state = "pepper" reagent_id = /datum/reagent/consumable/condensedcapsaicin MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/peppertank, 30) /obj/structure/reagent_dispensers/wall/peppertank/Initialize(mapload) . = ..() if(prob(1)) desc = "IT'S PEPPER TIME, BITCH!" find_and_hang_on_wall() /obj/structure/reagent_dispensers/water_cooler name = "liquid cooler" desc = "A machine that dispenses liquid to drink." icon = 'icons/obj/machines/vending.dmi' //SKYRAT EDIT - ICON OVERRIDDEN BY AESTHETICS - SEE MODULE icon_state = "water_cooler" anchored = TRUE tank_volume = 500 var/paper_cups = 25 //Paper cups left from the cooler /obj/structure/reagent_dispensers/water_cooler/examine(mob/user) . = ..() if (paper_cups > 1) . += "There are [paper_cups] paper cups left." else if (paper_cups == 1) . += "There is one paper cup left." else . += "There are no paper cups left." /obj/structure/reagent_dispensers/water_cooler/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return if(!paper_cups) to_chat(user, span_warning("There aren't any cups left!")) return user.visible_message(span_notice("[user] takes a cup from [src]."), span_notice("You take a paper cup from [src].")) var/obj/item/reagent_containers/cup/glass/sillycup/S = new(get_turf(src)) user.put_in_hands(S) paper_cups-- /obj/structure/reagent_dispensers/beerkeg name = "beer keg" desc = "Beer is liquid bread, it's good for you..." icon_state = "beer" reagent_id = /datum/reagent/consumable/ethanol/beer openable = TRUE /obj/structure/reagent_dispensers/beerkeg/blob_act(obj/structure/blob/B) explosion(src, heavy_impact_range = 3, light_impact_range = 5, flame_range = 10, flash_range = 7) if(!QDELETED(src)) qdel(src) /obj/structure/reagent_dispensers/wall/virusfood name = "virus food dispenser" desc = "A dispenser of low-potency virus mutagenic." icon_state = "virus_food" reagent_id = /datum/reagent/consumable/virus_food MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/virusfood, 30) /obj/structure/reagent_dispensers/wall/virusfood/Initialize(mapload) . = ..() find_and_hang_on_wall() /obj/structure/reagent_dispensers/cooking_oil name = "vat of cooking oil" desc = "A huge metal vat with a tap on the front. Filled with cooking oil for use in frying food." icon_state = "vat" anchored = TRUE reagent_id = /datum/reagent/consumable/nutriment/fat/oil openable = TRUE /obj/structure/reagent_dispensers/servingdish name = "serving dish" desc = "A dish full of food slop for your bowl." icon = 'icons/obj/service/kitchen.dmi' icon_state = "serving" anchored = TRUE reagent_id = /datum/reagent/consumable/nutraslop /obj/structure/reagent_dispensers/plumbed name = "stationary water tank" anchored = TRUE icon_state = "water_stationary" //SKYRAT EDIT - ICON OVERRIDDEN BY AESTHETICS - SEE MODULE desc = "A stationary, plumbed, water tank." can_be_tanked = FALSE /obj/structure/reagent_dispensers/plumbed/Initialize(mapload) . = ..() AddComponent(/datum/component/plumbing/simple_supply) /obj/structure/reagent_dispensers/plumbed/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) return ITEM_INTERACT_SUCCESS /obj/structure/reagent_dispensers/plumbed/storage name = "stationary storage tank" icon_state = "tank_stationary" reagent_id = null //start empty /obj/structure/reagent_dispensers/plumbed/storage/Initialize(mapload) . = ..() AddComponent(/datum/component/simple_rotation) /obj/structure/reagent_dispensers/plumbed/storage/update_overlays() . = ..() if(!reagents) return if(!reagents.total_volume) return var/mutable_appearance/tank_color = mutable_appearance('icons/obj/medical/chemical_tanks.dmi', "tank_chem_overlay") tank_color.color = mix_color_from_reagents(reagents.reagent_list) . += tank_color /obj/structure/reagent_dispensers/plumbed/fuel name = "stationary fuel tank" icon_state = "fuel_stationary" desc = "A stationary, plumbed, fuel tank." reagent_id = /datum/reagent/fuel accepts_rig = TRUE #undef REAGENT_SPILL_DIVISOR