Files
Casper3667 f31aeb6595 Misc disposal fixes (#22666)
Stickers can no longer be attached to disposal machinery or conveyors,
to prevent further issues with throwing them there.
The disposal outlet sprite was set to face the wrong direction when
expelling items, for two of its directions. This was fixed.
The disposal UI constantly reopening itself was fixed.
And a set of disposal systems were added to runtime for easier
debugging.
<img width="344" height="385" alt="image"
src="https://github.com/user-attachments/assets/ff93f3e9-bfa7-480c-9d6b-c501b02ca51a"
/>
2026-06-11 10:47:14 +00:00

1747 lines
48 KiB
Plaintext

/**
* Disposal bin
* Holds items for disposal into pipe system
* Draws air from turf, gradually charges internal reservoir
* Once full (~1 atm), uses air resv to flush items into the pipes
* Automatically recharges air (unless off), will flush when ready if pre-set
* Can hold items and human size things, no other draggables
* Toilets are a type of disposal bin for small objects only and work on magic. By magic, I mean torque rotation
*/
/// kPa - assume the inside of a dispoal pipe is 1 atm, so that needs to be added.
#define SEND_PRESSURE (700 + ONE_ATMOSPHERE)
/// (Liters L)
#define PRESSURE_TANK_VOLUME 150
/// L/s - 4 m/s using a 15 cm by 15 cm inlet
#define PUMP_MAX_FLOW_RATE 90
#define MODE_OFF 0
#define MODE_PRESSURIZING 1
#define MODE_READY 2
#define MODE_FLUSHING 3
/obj/structure/machinery/disposal
name = "disposal unit"
desc = "A pneumatic waste disposal unit."
icon = 'icons/obj/disposals.dmi'
icon_state = "disposal"
anchored = 1
density = 1
/// Unit is powered
var/is_on = TRUE
var/datum/wires/disposal/wires
/// Internal reservoir
var/datum/gas_mixture/air_contents
var/mode = MODE_PRESSURIZING
/// MODE_X define that was set when we last updated overlays.
var/mode_icon
/// Whether or not we are currently indicating as occupied.
var/showing_full = FALSE
/// Controlled by flush wire status
var/can_flush = TRUE
/// TRUE if flush handle is pulled
var/flush = FALSE
/// The attached pipe trunk
var/obj/structure/disposalpipe/trunk/trunk = null
/// TRUE if flushing in progress. To be replaced with mode entirely
var/flushing = 0
/// Every 30 ticks it will look whether it is ready to flush
var/flush_every_ticks = 30
/// This var adds 1 once per tick. When it reaches flush_every_ticks it resets and tries to flush
var/flush_count = 0
var/last_sound = 0
/// Most pumps require air to function, but some don't
var/uses_air = TRUE
/// The pneumatic pump power. 3 HP ~ 2200W
active_power_usage = 2200
idle_power_usage = 100
/obj/structure/machinery/disposal/airless
uses_air = FALSE
/obj/structure/machinery/disposal/small
desc = "A compact pneumatic waste disposal unit."
icon_state = "disposal_small"
density = FALSE
/obj/structure/machinery/disposal/small/north
dir = NORTH
pixel_y = -13
layer = MOB_LAYER + 0.1
/obj/structure/machinery/disposal/small/south
dir = SOUTH
pixel_y = 20
layer = OBJ_LAYER + 0.3
/obj/structure/machinery/disposal/small/east
dir = EAST
pixel_x = -12
/obj/structure/machinery/disposal/small/west
dir = WEST
pixel_x = 11
/obj/structure/machinery/disposal/small/airless
uses_air = FALSE
/obj/structure/machinery/disposal/small/Initialize()
. = ..()
if(pixel_x || pixel_y)
return
else
switch(dir)
if(NORTH)
pixel_y = -13
layer = MOB_LAYER + 0.1
if(SOUTH)
pixel_y = 20
layer = OBJ_LAYER + 0.3
if(EAST)
pixel_x = -12
if(WEST)
pixel_x = 11
/obj/structure/machinery/disposal/small/check_mob_size(mob/target)
if(target.mob_size > MOB_SMALL)
return FALSE
return TRUE
/**
* Create a new disposal:
* Find the attached trunk (if present), and initialize gas reservoir.
*/
/obj/structure/machinery/disposal/Initialize()
. = ..()
trunk = locate() in src.loc
if(!trunk)
mode = MODE_OFF
is_on = FALSE
flush = 0
else
trunk.linked = src // link the pipe trunk to self
wires = new(src)
air_contents = new/datum/gas_mixture(PRESSURE_TANK_VOLUME)
update()
/obj/structure/machinery/disposal/Destroy()
eject()
if(trunk)
trunk.linked = null
return ..()
/obj/structure/machinery/disposal/proc/contents_count()
var/things = 0
for(var/thing in contents)
if(istype(thing, /obj/item/assembly/signaler))
var/obj/item/assembly/signaler/S = thing
if(S.connected == wires)
continue
things++
return things
/**
* Attack by an item places it into the disposal.
*/
/obj/structure/machinery/disposal/attackby(obj/item/attacking_item, mob/user)
if(stat & BROKEN || !attacking_item || !user)
return
var/has_contents
src.add_fingerprint(user)
if(!is_on)
has_contents = contents_count()
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
if(has_contents)
to_chat(user, SPAN_WARNING("Eject the items first!"))
return TRUE
else if(default_deconstruction_screwdriver(user, attacking_item))
update()
return TRUE
else if(attacking_item.tool_behaviour == TOOL_WELDER && panel_open)
if(has_contents)
to_chat(user, SPAN_WARNING("Eject the items first!"))
return TRUE
var/obj/item/weldingtool/W = attacking_item
if(W.use(0,user))
to_chat(user, SPAN_NOTICE("You start slicing the floorweld off the disposal unit."))
if(W.use_tool(src, user, 20, volume = 50))
if(!src || !W.isOn())
return TRUE
to_chat(user, SPAN_NOTICE("You sliced the floorweld off the disposal unit."))
if(!istype(src, /obj/structure/machinery/disposal/small))
var/obj/structure/disposalconstruct/C = new (src.loc)
src.transfer_fingerprints_to(C)
C.ptype = 6 // 6 = disposal unit
C.anchored = 1
C.density = 1
C.update()
else
var/obj/structure/disposalconstruct/C = new (src.loc)
src.transfer_fingerprints_to(C)
C.ptype = 15 // 15 = small disposal unit
C.anchored = 1
C.update()
qdel(src)
return TRUE
else
to_chat(user, SPAN_WARNING("You need more welding fuel to complete this task."))
return TRUE
if(istype(attacking_item, /obj/item/melee/energy/blade))
to_chat(user, SPAN_WARNING("You can't place that item inside the disposal unit."))
return TRUE
if(istype(attacking_item, /obj/item/storage) && length(attacking_item.contents) && user.a_intent != I_HURT)
var/obj/item/storage/S = attacking_item
if(istype(S, /obj/item/storage/secure))
var/obj/item/storage/secure/secured_storage = S
if(secured_storage.locked)
to_chat(user, SPAN_WARNING("You can't empty \the [secured_storage] into \the [src]. It is locked."))
return TRUE
user.visible_message("<b>[user]</b> empties \the [S] into \the [src].", SPAN_NOTICE("You empty \the [S] into \the [src]."), range = 3)
for(var/obj/item/O in S.contents)
S.remove_from_storage(O, src)
S.update_icon()
update()
return TRUE
else if(istype(attacking_item, /obj/item/material/ashtray) && user.a_intent != I_HURT) // If attacked with ashtray on harm intent.
var/obj/item/material/ashtray/A = attacking_item
if(A.emptyout(src)) // Ashtray has contents.
user.visible_message("<b>[user]</b> pours \the [attacking_item] out into \the [src].", SPAN_NOTICE("You pour \the [attacking_item] out into \the [src]."))
else // Ashtray has no contents.
to_chat(user, SPAN_NOTICE("\The [attacking_item] is empty."))
return TRUE
else if (istype (attacking_item, /obj/item/lightreplacer))
var/count = 0
var/obj/item/lightreplacer/R = attacking_item
if (R.store_broken)
for(var/obj/item/light/L in R.contents)
count++
L.forceMove(src)
if (count)
to_chat(user, SPAN_NOTICE("You empty [count] broken bulbs into the disposal."))
else
to_chat(user, SPAN_NOTICE("There are no broken bulbs to empty out."))
update()
return TRUE
var/obj/item/grab/G = attacking_item
// handle grabbed mob
if(istype(G))
if(ismob(G.affecting))
var/mob/GM = G.affecting
if(!check_mob_size(GM))
to_chat(user, SPAN_NOTICE("The opening is too narrow for [G.affecting] to fit!"))
return TRUE
for (var/mob/V in viewers(usr))
V.show_message("[usr] starts putting [GM.name] into the disposal.", 3)
if(do_after(usr, 20))
if (GM.client)
GM.client.perspective = EYE_PERSPECTIVE
GM.client.eye = src
GM.forceMove(src)
for (var/mob/C in viewers(src))
C.show_message(SPAN_WARNING("[GM.name] has been placed in the [src] by [user]."), 3)
qdel(G)
usr.attack_log += "\[[time_stamp()]\] <span class='warning'>Has placed [GM.name] ([GM.ckey]) in disposals.</span>"
GM.attack_log += "\[[time_stamp()]\] <font color='orange'>Has been placed in disposals by [usr.name] ([usr.ckey])</font>"
msg_admin_attack("[key_name_admin(usr)] placed [key_name_admin(GM)] in a disposals unit. (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[usr.x];Y=[usr.y];Z=[usr.z]'>JMP</a>)",ckey=key_name(usr),ckey_target=key_name(GM))
return TRUE
if(!attacking_item.dropsafety())
return TRUE
if(!attacking_item)
return TRUE
user.drop_from_inventory(attacking_item, src)
user.visible_message("<b>[user]</b> places \the [attacking_item] into \the [src].", SPAN_NOTICE("You place \the [attacking_item] into \the [src]."), range = 3)
update()
/**
* Handles mouse-dropping another mob or self onto disposal.
*/
/obj/structure/machinery/disposal/mouse_drop_receive(atom/dropped, mob/user, params)
var/mob/target = dropped
if(!istype(target))
return
if(user.stat || !user.canmove || !istype(target))
return
if(target.buckled_to || get_dist(user, src) > 1 || get_dist(user, target) > 1)
return
// Animals cannot put mobs other than themselves into disposal
if(isanimal(user) && target != user)
return
if(!check_mob_size(target))
to_chat(user, SPAN_NOTICE("The opening is too narrow for [target] to fit!"))
return
// Makes it so synths can't be flushed
if (isrobot(target) && !isDrone(target))
to_chat(user, SPAN_NOTICE("[target] is a bit too clunky to fit!"))
return
src.add_fingerprint(user)
var/target_loc = target.loc
var/msg
for (var/mob/V in viewers(usr))
if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
V.show_message("[usr] starts climbing into the disposal.", 3)
if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
if(target.anchored) return
V.show_message("[usr] starts stuffing [target.name] into the disposal.", 3)
if(!do_after(usr, 20))
return
if(target_loc != target.loc)
return
// If drop self, then climbed in. Must be awake, not stunned or whatever
if(target == user && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
msg = "[user.name] climbs into the [src]."
to_chat(user, "You climb into the [src].")
else if(target != user && !user.restrained() && !user.stat && !user.weakened && !user.stunned && !user.paralysis)
msg = "[user.name] stuffs [target.name] into the [src]!"
to_chat(user, "You stuff [target.name] into the [src]!")
user.attack_log += "\[[time_stamp()]\] <span class='warning'>Has placed [target.name] ([target.ckey]) in disposals.</span>"
target.attack_log += "\[[time_stamp()]\] <font color='orange'>Has been placed in disposals by [user.name] ([user.ckey])</font>"
msg_admin_attack("[user] ([user.ckey]) placed [target] ([target.ckey]) in a disposals unit. (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)",ckey=key_name(user),ckey_target=key_name(target))
else
return
if (target.client)
target.client.perspective = EYE_PERSPECTIVE
target.client.eye = src
target.forceMove(src)
for (var/mob/C in viewers(src))
if(C == user)
continue
C.show_message(msg, 3)
update()
/obj/structure/machinery/disposal/proc/check_mob_size(mob/target)
return 1
/**
* Attempt to move while inside.
*/
/obj/structure/machinery/disposal/relaymove(mob/living/user, direction)
. = ..()
if(user.stat || src.mode == MODE_FLUSHING)
return
if(user.loc == src)
src.go_out(user)
return
/**
* Leave the disposal.
*/
/obj/structure/machinery/disposal/proc/go_out(mob/user)
if (user.client)
user.client.eye = user.client.mob
user.client.perspective = MOB_PERSPECTIVE
user.forceMove(src.loc)
update()
return
/**
* AI: as human but can't flush.
*/
/obj/structure/machinery/disposal/attack_ai(mob/user as mob)
if(!ai_can_interact(user))
return
var/inside_bin = (user.loc == src)
interact(user, !inside_bin)
/**
* Human interacts with machine.
*/
/obj/structure/machinery/disposal/attack_hand(mob/user as mob)
if(stat & BROKEN)
return
if(user.loc == src)
to_chat(user, SPAN_WARNING("You cannot reach the controls from here."))
return
interact(user)
/obj/structure/machinery/disposal/interact(mob/user)
if(!user)
return
src.add_fingerprint(user)
user.set_machine(src)
if(panel_open)
wires.interact(user)
return ui_interact(user)
/obj/structure/machinery/disposal/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "DisposalUnit", "Disposal Unit", 320, 200)
ui.open()
/obj/structure/machinery/disposal/ui_data(mob/user)
var/list/data = list()
data["is_on"] = is_on
data["flush"] = flush
data["mode"] = mode
data["uses_air"] = uses_air
data["panel_open"] = panel_open
data["pressure"] = CLAMP01(XGM_PRESSURE(air_contents) / (SEND_PRESSURE))
return data
/obj/structure/machinery/disposal/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("handle-0")
flush = FALSE
update()
. = TRUE
if("handle-1")
flush = TRUE
update()
. = TRUE
if("power")
is_on = !is_on
update()
. = TRUE
if("eject")
eject()
. = TRUE
/**
* Eject the contents of the disposal unit.
*/
/obj/structure/machinery/disposal/proc/eject()
for(var/atom/movable/AM in src)
AM.forceMove(src.loc)
AM.pipe_eject(0)
update()
/**
* Update the icon & overlays to reflect mode & status
*/
/obj/structure/machinery/disposal/proc/update()
if(!isnull(mode_icon) && (stat & BROKEN))
ClearOverlays()
icon_state = "[icon_state]-broken"
mode_icon = null
mode = MODE_OFF
flush = 0
return
var/has_contents = !!length(contents)
if (mode_icon == mode && showing_full == has_contents)
return
// if we're here, we must rebuild...
ClearOverlays()
// flush handle
if(flush)
AddOverlays("[icon_state]-handle")
// only handle is shown if no power
if(stat & NOPOWER || mode == MODE_OFF)
mode_icon = MODE_OFF
return
// check for items in disposal - occupied light
if(has_contents)
AddOverlays("[icon_state]-full")
showing_full = has_contents
// charging and ready light
if(mode == MODE_PRESSURIZING)
AddOverlays("[icon_state]-charge")
mode_icon = MODE_PRESSURIZING
else if(mode == MODE_READY)
AddOverlays("[icon_state]-ready")
mode_icon = MODE_READY
/**
* Timed process. Charge the gas reservoir and perform flush if ready.
*/
/obj/structure/machinery/disposal/process()
if((stat & BROKEN) || !is_on)
update_use_power(POWER_USE_OFF)
return
flush_count++
// We've hit the flush counter automatically
if(flush_count >= flush_every_ticks)
if(length(contents))
if(mode == MODE_READY)
spawn(0)
feedback_inc("disposal_auto_flush",1)
flush()
flush_count = 0
SStgui.update_uis(src)
// If we're ready, don't draw any extra power
if(mode == MODE_READY || !uses_air)
update_use_power(POWER_USE_IDLE)
// We used the manual flush button
if(flush)
flush()
return
update()
// Validate whether we're pressurized or not.
if(mode == MODE_PRESSURIZING && XGM_PRESSURE(air_contents) >= SEND_PRESSURE)
mode = MODE_READY
update()
return
// If you turn this into a bare 'else' statement it just tries to pressurize infinitely and I don't know why.
else if(mode == MODE_PRESSURIZING && XGM_PRESSURE(air_contents) < SEND_PRESSURE)
src.pressurize()
update()
return
/**
* If powered and working, transfer gas from local env to internal reservoir and use the required power to do so.
*/
/obj/structure/machinery/disposal/proc/pressurize()
// Don't pressurize if there's no power.
if(stat & NOPOWER)
update_use_power(POWER_USE_OFF)
return
// Recharge from loc turf
var/atom/L = loc
if(!loc) return
var/datum/gas_mixture/env = L.return_air()
var/power_draw = -1
if(env && env.temperature > 0)
// Group_multiplier is divided out here
var/transfer_moles = (PUMP_MAX_FLOW_RATE/env.volume)*env.total_moles
// Using power, pump air from local tile into itself
power_draw = pump_gas(src, env, air_contents, transfer_moles, active_power_usage)
if (power_draw > 0)
use_power_oneoff(power_draw)
// If we've reached the target pressure, we're ready to flush
if(XGM_PRESSURE(air_contents) >= SEND_PRESSURE)
mode = MODE_READY
/**
* Attempt to flush. If able, create a virtual holder object containing disposal bin & gas reservoir contents to ship through disposals network
*/
/obj/structure/machinery/disposal/proc/flush()
set waitfor = FALSE
if(!can_flush)
shake_animation()
visible_message(SPAN_WARNING("\The [src] groans violently!"), range = 3)
flush = FALSE
return
intent_message(MACHINE_SOUND)
mode = MODE_FLUSHING
flick("[icon_state]-flush", src)
var/wrapcheck = 0
var/obj/disposalholder/H = new() // virtual holder object which actually
// travels through the pipes.
//Hacky test to get drones to mail themselves through disposals.
for(var/mob/living/silicon/robot/drone/D in src)
wrapcheck = 1
for(var/obj/item/smallDelivery/O in src)
wrapcheck = 1
if(wrapcheck == 1)
H.tomail = 1
sleep(10)
if(last_sound < world.time + 1)
playsound(src, 'sound/machines/disposalflush.ogg', 50, 0, 0)
last_sound = world.time
// Wait for animation to finish
sleep(5)
// Copy the contents of disposer to holder
H.init(src, air_contents)
// New empty gas reservoir
air_contents = new(PRESSURE_TANK_VOLUME)
// Start the holder processing movement
H.start(src)
// Now reset disposal state
flush = 0
if(mode == MODE_FLUSHING)
if(uses_air)
mode = MODE_PRESSURIZING
else
mode = MODE_READY
update()
return
/**
* Called when area power changes.
*/
/obj/structure/machinery/disposal/power_change()
// do default setting/reset of stat NOPOWER bit
..()
// update icon
update()
return
/**
* Called when holder is expelled from a disposal- should usually only occur if the pipe network is modified
*/
/obj/structure/machinery/disposal/proc/expel(var/obj/disposalholder/H)
var/turf/target
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
// Somehow, someone managed to flush a window which broke mid-transit and caused the disposal to go in an infinite loop trying to expel null, hopefully this fixes it
if(H)
for(var/atom/movable/AM in H)
target = get_offset_target_turf(src.loc, rand(5)-rand(5), rand(5)-rand(5))
AM.forceMove(src.loc)
AM.pipe_eject(0)
// Poor drones kept smashing windows and taking system damage being fired out of disposals. ~Z
if(!istype(AM,/mob/living/silicon/robot/drone))
addtimer(CALLBACK(AM, TYPE_PROC_REF(/atom/movable, throw_at), target, 5, 1), 1)
H.vent_gas(loc)
qdel(H)
/obj/structure/machinery/disposal/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(mover?.movement_type & PHASING)
return TRUE
if(istype(mover, /obj/projectile))
return 1
if(istype(mover,/obj/item) && mover.throwing)
return 0
else
return ..(mover, target, height, air_group)
/obj/structure/machinery/disposal/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(isitem(hitting_atom))
if(prob(75))
hitting_atom.forceMove(src)
visible_message(SPAN_NOTICE("[hitting_atom] lands in [src]."))
else
visible_message(SPAN_NOTICE("[hitting_atom] bounces off of [src]'s rim!"))
return ..()
else
return ..()
/obj/structure/machinery/disposal/can_attach_sticker(mob/user, obj/item/sticker/S)
return FALSE
/**
* Virtual disposal object
* Travels through pipes in lieu of actual items
* Contents will be items flushed by the disposal
* This allows the gas flushed to be tracked
*/
/obj/disposalholder
invisibility = 101
/// Gas used to flush, will appear at exit point
var/datum/gas_mixture/gas = null
dir = 0
/// Can travel 2048 steps before going inactive (in case of loops)
var/count = 2048
/// Vhanges if contains a delivery container
var/destinationTag = ""
/// Changes if contains wrapped package
var/tomail = 0
/// If it contains a mob
var/hasmob = 0
/// Set by a partial tagger the first time round, then put in destinationTag if it goes through again.
var/partialTag = ""
var/tmp/obj/structure/disposalpipe/tick_last
/**
* Initialize a holder from the contents of a disposal unit.
*/
/obj/disposalholder/proc/init(var/obj/structure/machinery/disposal/D, var/datum/gas_mixture/flush_gas)
gas = flush_gas// transfer gas resv. into holder object -- let's be explicit about the data this proc consumes, please.
//Check for any living mobs trigger hasmob.
//hasmob effects whether the package goes to cargo or its tagged destination.
for(var/mob/living/M in D)
if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
hasmob = 1
//Checks 1 contents level deep. This means that players can be sent through disposals...
//...but it should require a second person to open the package. (i.e. person inside a wrapped locker)
for(var/obj/O in D)
if(O.contents)
for(var/mob/living/M in O.contents)
if(M && M.stat != 2 && !istype(M,/mob/living/silicon/robot/drone))
hasmob = 1
// now everything inside the disposal gets put into the holder
// note AM since can contain mobs or objs
for(var/atom/movable/AM in D)
if(istype(AM, /obj/item/assembly/signaler))
var/obj/item/assembly/signaler/S = AM
if(S.connected == D.wires)
continue
AM.forceMove(src)
if(istype(AM, /obj/structure/bigDelivery) && !hasmob)
var/obj/structure/bigDelivery/T = AM
src.destinationTag = T.sortTag
if(istype(AM, /obj/item/smallDelivery) && !hasmob)
var/obj/item/smallDelivery/T = AM
src.destinationTag = T.sortTag
//Drones can mail themselves through maint.
if(istype(AM, /mob/living/silicon/robot/drone))
var/mob/living/silicon/robot/drone/drone = AM
src.destinationTag = drone.mail_destination
/**
* Start the movement process
* Argument is the disposal unit the holder started in
*/
/obj/disposalholder/proc/start(var/obj/structure/machinery/disposal/D)
// No trunk connected, so expel immediately
if(!D.trunk)
D.expel(src)
return
forceMove(D.trunk)
set_dir(DOWN)
START_PROCESSING(SSdisposals, src)
/**
* For the new SSdisposals-based movement.
*/
/obj/disposalholder/process()
if (hasmob && prob(3))
for(var/mob/living/H in src)
// Drones use the mailing code to move through the disposal system,
if(!istype(H,/mob/living/silicon/robot/drone))
// Horribly maim any living creature jumping down disposals. C'est la vie
H.take_overall_damage(20, 0, DAMAGE_FLAG_SHARP, "Blunt Trauma")
var/obj/structure/disposalpipe/curr = loc
if (!loc)
STOP_PROCESSING(SSdisposals, src)
tick_last = null
crash_with("disposalholder processing in nullspace!")
return
tick_last = curr
curr = curr.transfer(src)
if (!loc)
STOP_PROCESSING(SSdisposals, src)
tick_last = null
return
if (!curr)
//spawn (0) // expel() can sleep, so we gotta fork to not upset SSdisposals.
STOP_PROCESSING(SSdisposals, src)
tick_last.expel(src, get_turf(loc), dir)
if (!(count--))
STOP_PROCESSING(SSdisposals, src)
tick_last = null
/**
* Find the turf which should contain the next pipe
*/
/obj/disposalholder/proc/nextloc()
return get_step(loc,dir)
/**
* Find a matching pipe on a turf
*/
/obj/disposalholder/proc/findpipe(var/turf/T)
if(!T)
return null
// flip the movement direction
var/fdir = turn(dir, 180)
for(var/obj/structure/disposalpipe/P in T)
// find pipe direction mask that matches flipped dir
if(fdir & P.dpdir)
return P
// if no matching pipe, return null
return null
/**
* Merge two holder objects. Used when a a holder meets a stuck holder.
*/
/obj/disposalholder/proc/merge(obj/disposalholder/other)
for(var/atom/movable/AM in other)
// move everything in other holder to this one
AM.forceMove(src)
if(ismob(AM))
var/mob/M = AM
// if a client mob, update eye to follow this holder
if(M.client)
M.client.eye = src
qdel(other)
/obj/disposalholder/proc/settag(new_tag)
destinationTag = new_tag
/obj/disposalholder/proc/setpartialtag(new_tag)
if(partialTag == new_tag)
destinationTag = new_tag
partialTag = ""
else
partialTag = new_tag
/**
* Called when player tries to move while in a pipe.
*/
/obj/disposalholder/relaymove(mob/living/user, direction)
. = ..()
if(!istype(user,/mob/living))
return
var/mob/living/U = user
if (U.stat || U.last_special <= world.time)
return
U.last_special = world.time+100
if (src.loc)
for (var/mob/M in hearers(src.loc.loc))
to_chat(M, "<FONT size=[max(0, 5 - get_dist(src, M))]>CLONG, clong!</FONT>")
playsound(src.loc, 'sound/effects/clang.ogg', 50, 0, 0)
/**
* Called to vent all gas in holder to a location
*/
/obj/disposalholder/proc/vent_gas(atom/location)
location.assume_air(gas) // vent all gas to turf
/obj/disposalholder/Destroy()
STOP_PROCESSING(SSdisposals, src)
QDEL_NULL(gas)
tick_last = null
return ..()
// Disposal pipes
/obj/structure/disposalpipe
icon = 'icons/obj/disposals.dmi'
name = "disposal pipe"
desc = "An underfloor disposal pipe."
anchored = 1
density = 0
maxhealth = OBJECT_HEALTH_FRAGILE
level = 1 // underfloor only
var/dpdir = 0 // bitmask of pipe directions
//dir = 0 // dir will contain dominant direction for junction pipes
layer = EXPOSED_DISPOSALS_PIPE_LAYER
var/sortType = ""
var/subtype = 0
// new pipe, set the icon_state as on map
/obj/structure/disposalpipe/Initialize(mapload)
. = ..()
if(mapload)
var/turf/T = loc
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
I.layer = ABOVE_TILE_LAYER
I.alpha = 125
LAZYADD(T.blueprints, I)
/**
* Pipe is deleted. Ensure if holder is present, it is expelled.
*/
/obj/structure/disposalpipe/Destroy()
var/obj/disposalholder/H = locate() in src
if(H)
// holder was present
STOP_PROCESSING(SSdisposals, H)
var/turf/T = src.loc
if(T.density)
// deleting pipe is inside a dense turf (wall)
// this is unlikely, but just dump out everything into the turf in case
for(var/atom/movable/AM in H)
AM.forceMove(T)
AM.pipe_eject(0)
qdel(H)
return ..()
// otherwise, do normal expel from turf
if(H)
expel(H, T, 0)
return ..()
/**
* Returns the direction of the next pipe object, given the entrance dir
* By default, returns the bitmask of remaining directions
*/
/obj/structure/disposalpipe/proc/nextdir(var/fromdir)
return dpdir & (~turn(fromdir, 180))
/**
* Transfer the holder through this pipe segment
* Overriden for special behaviour
*/
/obj/structure/disposalpipe/proc/transfer(var/obj/disposalholder/H)
var/nextdir = nextdir(H.dir)
H.set_dir(nextdir)
var/turf/T = H.nextloc()
var/obj/structure/disposalpipe/P = H.findpipe(T)
if(P)
// find other holder in next loc, if inactive merge it with current
var/obj/disposalholder/H2 = locate() in P
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
H.merge(H2)
H.forceMove(P)
else // if wasn't a pipe, then set loc to turf
H.forceMove(T)
return null
return P
/**
* Update the icon_state to reflect hidden status
*/
/obj/structure/disposalpipe/proc/update()
var/turf/T = src.loc
hide(!T.is_plating() && !istype(T,/turf/space)) // space never hides pipes
/**
* Hide called by levelupdate if turf intact status changes
* Change visibility status and force update of icon
*/
/obj/structure/disposalpipe/hide(var/intact)
set_invisibility(intact ? 101: 0) // hide if floor is intact
/**
* Expel the held objects into a turf
* Called when there is a break in the pipe
*/
/obj/structure/disposalpipe/proc/expel(var/obj/disposalholder/H, var/turf/T, var/direction)
if(!istype(H) || !istype(T))
return
// Empty the holder if it is expelled into a dense turf.
// Leaving it intact and sitting in a wall is stupid.
if(T.density)
for(var/atom/movable/AM in H)
AM.forceMove(T)
AM.pipe_eject(0)
qdel(H)
return
if(!T.is_plating() && istype(T,/turf/simulated/floor)) //intact floor, pop the tile
var/turf/simulated/floor/F = T
F.break_tile()
new /obj/item/stack/tile(H) // add to holder so it will be thrown with other stuff
var/turf/target
if(direction) // direction is specified
if(istype(T, /turf/space)) // if ended in space, then range is unlimited
target = get_edge_target_turf(T, direction)
else // otherwise limit to 10 tiles
target = get_ranged_target_turf(T, direction, 10)
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
if(H)
for(var/atom/movable/AM in H)
AM.forceMove(T)
AM.pipe_eject(direction)
// addtimer will check AM for null.
addtimer(CALLBACK(AM, TYPE_PROC_REF(/atom/movable, throw_at), target, 100, 1), 1)
H.vent_gas(T)
qdel(H)
else // no specified direction, so throw in random direction
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
if(H)
for(var/atom/movable/AM in H)
target = get_offset_target_turf(T, rand(5)-rand(5), rand(5)-rand(5))
AM.forceMove(T)
AM.pipe_eject(0)
addtimer(CALLBACK(AM, TYPE_PROC_REF(/atom/movable, throw_at), target, 5, 1), 1)
H.vent_gas(T) // all gas vent to turf
qdel(H)
/**
* Call to break the pipe: will expel any holder inside at the time then delete the pipe
* remains: Set to leave broken pipe pieces in place.
*/
/obj/structure/disposalpipe/proc/broken(var/remains = FALSE)
if(remains)
for(var/D in GLOB.cardinals)
if(D & dpdir)
var/obj/structure/disposalpipe/broken/P = new(src.loc)
P.set_dir(D)
set_invisibility(101) // make invisible (since we won't delete the pipe immediately)
var/obj/disposalholder/H = locate() in src
if(H)
// holder was present
STOP_PROCESSING(SSdisposals, H)
var/turf/T = src.loc
if(T.density)
// broken pipe is inside a dense turf (wall)
// this is unlikely, but just dump out everything into the turf in case
for(var/atom/movable/AM in H)
AM.forceMove(T)
AM.pipe_eject(0)
qdel(H)
return
// otherwise, do normal expel from turf
if(H)
expel(H, T, 0)
QDEL_IN(src, 2) // delete pipe after 2 ticks to ensure expel proc finished
/**
* Pipe is affected by an explosion
*/
/obj/structure/disposalpipe/ex_act(severity)
switch(severity)
if(1.0)
broken(FALSE)
if(2.0)
add_damage(rand(5,15))
if(3.0)
add_damage(rand(0,15))
/obj/structure/disposalpipe/on_death(damage, damage_flags, damage_type, armor_penetration, obj/weapon)
if(health < -2)
broken(FALSE)
else if(health < 1)
broken(TRUE)
/**
* Attack by item
* Welding tool: Unfasten and convert to obj/disposalconstruct
*/
/obj/structure/disposalpipe/attackby(obj/item/attacking_item, mob/user)
var/turf/T = src.loc
if(!T.is_plating())
return // prevent interaction with T-scanner revealed pipes
src.add_fingerprint(user)
if(attacking_item.tool_behaviour == TOOL_WELDER)
var/obj/item/weldingtool/W = attacking_item
if(W.use(0,user))
playsound(src.loc, 'sound/items/welder_pry.ogg', 100, 1)
// check if anything changed over 3 seconds
to_chat(user, "Slicing the disposal pipe...")
if(W.use_tool(src, user, 30, volume = 50))
if(!src || !W.isOn()) return
welded()
else
to_chat(user, "You must stay still while welding the pipe.")
else
to_chat(user, "You need more welding fuel to cut the pipe.")
/**
* Called when pipe is cut by a welder.
*/
/obj/structure/disposalpipe/proc/welded()
var/obj/structure/disposalconstruct/C = new (src.loc)
switch(icon_state)
if("pipe-s")
C.ptype = 0
if("pipe-c")
C.ptype = 1
if("pipe-j1")
C.ptype = 2
if("pipe-j2")
C.ptype = 3
if("pipe-y")
C.ptype = 4
if("pipe-t")
C.ptype = 5
if("pipe-j1s")
C.ptype = 9
C.sortType = sortType
if("pipe-j2s")
C.ptype = 10
C.sortType = sortType
///// Z-Level stuff
if("pipe-u")
C.ptype = 11
if("pipe-d")
C.ptype = 12
///// Z-Level stuff
if("pipe-tagger")
C.ptype = 13
if("pipe-tagger-partial")
C.ptype = 14
C.subtype = src.subtype
src.transfer_fingerprints_to(C)
C.set_dir(dir)
C.density = 0
C.anchored = 1
C.update()
qdel(src)
/obj/structure/disposalpipe/hides_under_flooring()
return 1
// *** TEST verb
//client/verb/dispstop()
// for(var/obj/disposalholder/H in world)
// H.active = 0
/// a straight or bent segment
/obj/structure/disposalpipe/segment
icon_state = "pipe-s"
/obj/structure/disposalpipe/segment/Initialize()
. = ..()
if(icon_state == "pipe-s")
dpdir = dir | turn(dir, 180)
else
dpdir = dir | turn(dir, -90)
update()
/// Z-Level stuff
/obj/structure/disposalpipe/up
icon_state = "pipe-u"
/obj/structure/disposalpipe/up/Initialize()
. = ..()
dpdir = dir
update()
/obj/structure/disposalpipe/up/nextdir(var/fromdir)
var/nextdir
if(fromdir == 11)
nextdir = dir
else
nextdir = 12
return nextdir
/obj/structure/disposalpipe/up/transfer(var/obj/disposalholder/H)
var/nextdir = nextdir(H.dir)
H.set_dir(nextdir)
var/turf/T
var/obj/structure/disposalpipe/P
if(nextdir == 12)
var/turf/current_turf = get_turf(src)
T = GET_TURF_ABOVE(current_turf)
if(!T)
H.forceMove(loc)
return
else
for(var/obj/structure/disposalpipe/down/F in T)
P = F
else
T = get_step(src.loc, H.dir)
P = H.findpipe(T)
if(P)
// find other holder in next loc, if inactive merge it with current
var/obj/disposalholder/H2 = locate() in P
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
H.merge(H2)
H.forceMove(P)
else // if wasn't a pipe, then set loc to turf
H.forceMove(T)
return null
return P
/obj/structure/disposalpipe/down
icon_state = "pipe-d"
/obj/structure/disposalpipe/down/Initialize()
. = ..()
dpdir = dir
update()
/obj/structure/disposalpipe/down/nextdir(var/fromdir)
var/nextdir
if(fromdir == 12)
nextdir = dir
else
nextdir = 11
return nextdir
/obj/structure/disposalpipe/down/transfer(var/obj/disposalholder/H)
var/nextdir = nextdir(H.dir)
H.dir = nextdir
var/turf/T
var/obj/structure/disposalpipe/P
if(nextdir == 11)
var/turf/current_turf = get_turf(src)
T = GET_TURF_BELOW(current_turf)
if(!T)
H.forceMove(src.loc)
return
else
for(var/obj/structure/disposalpipe/up/F in T)
P = F
else
T = get_step(src.loc, H.dir)
P = H.findpipe(T)
if(P)
// find other holder in next loc, if inactive merge it with current
var/obj/disposalholder/H2 = locate() in P
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
H.merge(H2)
H.forceMove(P)
else // if wasn't a pipe, then set loc to turf
H.forceMove(T)
return null
return P
// Z-Level stuff
/obj/structure/disposalpipe/junction/yjunction
icon_state = "pipe-y"
///a three-way junction with dir being the dominant direction
/obj/structure/disposalpipe/junction
icon_state = "pipe-j1"
/obj/structure/disposalpipe/junction/Initialize()
. = ..()
if(icon_state == "pipe-j1")
dpdir = dir | turn(dir, -90) | turn(dir,180)
else if(icon_state == "pipe-j2")
dpdir = dir | turn(dir, 90) | turn(dir,180)
else // pipe-y
dpdir = dir | turn(dir,90) | turn(dir, -90)
update()
/*
* Next direction to move:
* If coming in from secondary dirs, then next is primary dir.
* If coming in from primary dir, then next is equal chance of other dirs.
*/
/obj/structure/disposalpipe/junction/nextdir(var/fromdir)
var/flipdir = turn(fromdir, 180)
if(flipdir != dir) // came from secondary dir
return dir // so exit through primary
else // came from primary
// so need to choose either secondary exit
var/mask = ..(fromdir)
// find a bit which is set
var/setbit = 0
if(mask & NORTH)
setbit = NORTH
else if(mask & SOUTH)
setbit = SOUTH
else if(mask & EAST)
setbit = EAST
else
setbit = WEST
if(prob(50)) // 50% chance to choose the found bit or the other one
return setbit
else
return mask & (~setbit)
/obj/structure/disposalpipe/tagger
name = "package tagger"
icon_state = "pipe-tagger"
var/sort_tag = ""
var/partial = 0
var/no_override = 0
/obj/structure/disposalpipe/tagger/proc/updatedesc()
desc = initial(desc)
if(sort_tag)
desc += "\nIt's tagging objects with the '[sort_tag]' tag."
/obj/structure/disposalpipe/tagger/proc/updatename()
if(sort_tag)
name = "[initial(name)] ([sort_tag])"
else
name = initial(name)
/obj/structure/disposalpipe/tagger/Initialize()
. = ..()
dpdir = dir | turn(dir, 180)
if(sort_tag)
SSdisposals.tagger_locations |= sort_tag
updatename()
updatedesc()
update()
/obj/structure/disposalpipe/tagger/attackby(obj/item/attacking_item, mob/user)
if(..())
return
if(istype(attacking_item, /obj/item/destTagger))
var/obj/item/destTagger/O = attacking_item
if(O.currTag)// Tag set
sort_tag = O.currTag
playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1)
to_chat(user, SPAN_NOTICE("Changed tag to '[sort_tag]'."))
updatename()
updatedesc()
/obj/structure/disposalpipe/tagger/transfer(var/obj/disposalholder/H)
if(sort_tag)
if(!no_override || H.destinationTag == "")
if(partial)
H.setpartialtag(sort_tag)
else
H.settag(sort_tag)
return ..()
/obj/structure/disposalpipe/tagger/partial //needs two passes to tag
name = "partial package tagger"
desc = "A unique desitnation tagger that requires an object to pass 2 times to tag."
icon_state = "pipe-tagger-partial"
partial = 1
///a three-way junction that sorts objects
/obj/structure/disposalpipe/sortjunction
name = "sorting junction"
icon_state = "pipe-j1s"
desc = "An underfloor disposal pipe with a package sorting mechanism."
var/posdir = 0
var/negdir = 0
var/sortdir = 0
/obj/structure/disposalpipe/sortjunction/proc/updatedesc()
desc = initial(desc)
if(sortType)
desc += "\nIt's filtering objects with the '[sortType]' tag."
/obj/structure/disposalpipe/sortjunction/proc/updatename()
if(sortType)
name = "[initial(name)] ([sortType])"
else
name = initial(name)
/obj/structure/disposalpipe/sortjunction/proc/updatedir()
posdir = dir
negdir = turn(posdir, 180)
if(icon_state == "pipe-j1s")
sortdir = turn(posdir, -90)
else if(icon_state == "pipe-j2s")
sortdir = turn(posdir, 90)
dpdir = sortdir | posdir | negdir
/obj/structure/disposalpipe/sortjunction/Initialize()
. = ..()
if(sortType)
SSdisposals.tagger_locations |= sortType
updatedir()
updatename()
updatedesc()
update()
/obj/structure/disposalpipe/sortjunction/attackby(obj/item/attacking_item, mob/user)
if(..())
return
if(istype(attacking_item, /obj/item/destTagger))
var/obj/item/destTagger/O = attacking_item
if(O.currTag)// Tag set
sortType = O.currTag
playsound(src.loc, 'sound/machines/twobeep.ogg', 100, 1)
to_chat(user, SPAN_NOTICE("Changed filter to '[sortType]'."))
updatename()
updatedesc()
/obj/structure/disposalpipe/sortjunction/proc/divert_check(var/checkTag)
return sortType == checkTag
/**
* Next direction to move:
* If coming in from negdir, then next is primary dir or sortdir.
* If coming in from posdir, then flip around and go back to posdir.
* If coming in from sortdir, go to posdir.
*/
/obj/structure/disposalpipe/sortjunction/nextdir(var/fromdir, var/sortTag)
if(fromdir != sortdir) // probably came from the negdir
if(divert_check(sortTag))
return sortdir
else
return posdir
else // came from sortdir
// so go with the flow to positive direction
return posdir
/obj/structure/disposalpipe/sortjunction/transfer(var/obj/disposalholder/H)
var/nextdir = nextdir(H.dir, H.destinationTag)
H.set_dir(nextdir)
var/turf/T = H.nextloc()
var/obj/structure/disposalpipe/P = H.findpipe(T)
if(P)
// find other holder in next loc, if inactive merge it with current
var/obj/disposalholder/H2 = locate() in P
if(H2 && !(H2.datum_flags & DF_ISPROCESSING))
H.merge(H2)
H.forceMove(P)
else // if wasn't a pipe, then set loc to turf
H.forceMove(T)
return null
return P
/// a three-way junction that filters all wrapped and tagged items
/obj/structure/disposalpipe/sortjunction/wildcard
name = "tagged sorting junction"
desc = "An underfloor disposal pipe which filters all wrapped and tagged items."
subtype = 1
/obj/structure/disposalpipe/sortjunction/wildcard/divert_check(var/checkTag)
return checkTag != ""
/// junction that filters all untagged items
/obj/structure/disposalpipe/sortjunction/untagged
name = "untagged sorting junction"
desc = "An underfloor disposal pipe which filters all untagged items."
subtype = 2
/obj/structure/disposalpipe/sortjunction/untagged/divert_check(var/checkTag)
return checkTag == ""
/obj/structure/disposalpipe/sortjunction/flipped //for easier and cleaner mapping
icon_state = "pipe-j2s"
/obj/structure/disposalpipe/sortjunction/wildcard/flipped
icon_state = "pipe-j2s"
/obj/structure/disposalpipe/sortjunction/untagged/flipped
icon_state = "pipe-j2s"
/// a trunk joining to a disposal bin or outlet on the same turf
/obj/structure/disposalpipe/trunk
icon_state = "pipe-t"
var/obj/linked // the linked obj/structure/machinery/disposal or obj/disposaloutlet
/obj/structure/disposalpipe/trunk/Initialize()
. = ..()
dpdir = dir
update()
getlinked()
/obj/structure/disposalpipe/trunk/proc/getlinked()
linked = null
var/obj/structure/machinery/disposal/D = locate() in src.loc
if(D)
linked = D
if (!D.trunk)
D.trunk = src
var/obj/structure/disposaloutlet/O = locate() in src.loc
if(O)
linked = O
update()
/**
* Override attackby so we disallow trunkremoval when somethings ontop
*/
/obj/structure/disposalpipe/trunk/attackby(obj/item/attacking_item, mob/user)
//Disposal bins or chutes
/*
These shouldn't be required
var/obj/structure/machinery/disposal/D = locate() in src.loc
if(D && D.anchored)
return
//Disposal outlet
var/obj/structure/disposaloutlet/O = locate() in src.loc
if(O && O.anchored)
return
*/
//Disposal constructors
var/obj/structure/disposalconstruct/C = locate() in src.loc
if(C && C.anchored)
return
var/turf/T = src.loc
if(!T.is_plating())
return // prevent interaction with T-scanner revealed pipes
src.add_fingerprint(user)
if(attacking_item.tool_behaviour == TOOL_WELDER)
var/obj/item/weldingtool/W = attacking_item
if(W.use(0,user))
playsound(src.loc, 'sound/items/welder_pry.ogg', 100, 1)
// check if anything changed over 3 seconds
to_chat(user, "Slicing the disposal pipe.")
if(W.use_tool(src, user, 30, volume = 50))
if(!src || !W.isOn()) return
welded()
else
to_chat(user, "You must stay still while welding the pipe.")
else
to_chat(user, "You need more welding fuel to cut the pipe.")
/*
* Would transfer to next pipe segment, but we are in a trunk.
* If not entering from disposal bin, transfer to linked object (outlet or bin)
*/
/obj/structure/disposalpipe/trunk/transfer(var/obj/disposalholder/H)
if(H.dir == DOWN) // we just entered from a disposer
return ..() // so do base transfer proc
// otherwise, go to the linked object
if(linked)
var/obj/structure/disposaloutlet/O = linked
if(istype(O) && (H))
O.expel(H) // expel at outlet
else
var/obj/structure/machinery/disposal/D = linked
if(H)
D.expel(H) // expel at disposal
else
if(H)
src.expel(H, get_turf(src), 0) // expel at turf
return null
// nextdir
/obj/structure/disposalpipe/trunk/nextdir(var/fromdir)
if(fromdir == DOWN)
return dir
else
return 0
/obj/structure/disposalpipe/trunk/expel(obj/disposalholder/H)
if (!linked)
..(H)
/// A broken pipe.
/obj/structure/disposalpipe/broken
icon_state = "pipe-b"
dpdir = 0 // broken pipes have dpdir=0 so they're not found as 'real' pipes
// i.e. will be treated as an empty turf
desc = "A broken piece of disposal pipe."
/obj/structure/disposalpipe/broken/Initialize()
. = ..()
update()
/**
* Called when welded
* For broken pipe, remove and turn into scrap
*/
/obj/structure/disposalpipe/broken/welded()
// var/obj/item/scrap/S = new(src.loc)
// S.set_components(200,0,0)
qdel(src)
/// The disposal outlet machine
/obj/structure/disposaloutlet
name = "disposal outlet"
desc = "An outlet for the pneumatic disposal system."
icon = 'icons/obj/disposals.dmi'
icon_state = "outlet"
density = 1
anchored = 1
var/active = 0
var/turf/target // this will be where the output objects are 'thrown' to.
var/mode = 0
var/spread = 0
var/spread_point = 3
/obj/structure/disposaloutlet/Initialize()
. = ..()
target = get_ranged_target_turf(src, dir, spread_point)
var/obj/structure/disposalpipe/trunk/trunk = locate() in src.loc
if(trunk)
trunk.linked = src // link the pipe trunk to self
/proc/disposal_log(thing)
LOG_DEBUG("\[[world.time]] Disposals: [thing]")
/obj/structure/disposaloutlet/proc/expel(var/obj/disposalholder/H)
set waitfor = FALSE
flick("outlet-open", src)
playsound(src, 'sound/machines/warning-buzzer.ogg', 50, 0, 0)
sleep(20) //wait until correct animation frame
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
intent_message(THUNK_SOUND)
if(H)
for(var/atom/movable/AM in H)
AM.forceMove(src.loc)
AM.pipe_eject(dir)
if(!istype(AM,/mob/living/silicon/robot/drone)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z
spawn(5)
if(spread)
var/turf/new_turf_target = get_step(target,turn(src.dir, rand(-spread,spread)))
AM.throw_at(new_turf_target, spread_point, 1)
else
AM.throw_at(target, spread_point, 1)
H.vent_gas(src.loc)
qdel(H)
return
/*
// expel the contents of the holder object, then delete it
// called when the holder exits the outlet
/obj/structure/disposaloutlet/proc/expel(var/obj/structure/disposalholder/H)
disposal_log("[src] [REF(src)] expel([REF(H)])")
flick("outlet-open", src)
playsound(src, 'sound/machines/warning-buzzer.ogg', 50, 0, 0)
disposal_log("[src] ([REF(src)]) registering timers.")
addtimer(CALLBACK(src, PROC_REF(post_expel, H)), 20, TIMER_UNIQUE|TIMER_CLIENT_TIME) // Sound + gas.
addtimer(CALLBACK(src, PROC_REF(post_post_expel, H)), 20 + 5, TIMER_UNIQUE|TIMER_CLIENT_TIME) // Actually throwing the items.
/obj/structure/disposaloutlet/proc/post_expel(obj/structure/disposalholder/H)
playsound(src, 'sound/machines/hiss.ogg', 50, 0, 0)
disposal_log("[src] ([REF(src)]) post_expel() timer fired.")
if(H)
H.vent_gas(src.loc)
/obj/structure/disposaloutlet/proc/throw_object(atom/movable/thing)
disposal_log("[src] ([REF(src)]) throw_object([thing] [REF(thing)]) at [target] [REF(target)] origin [loc] [REF(loc)]")
thing.forceMove(loc)
thing.pipe_eject(dir)
if (!istype(thing, /mob/living/silicon/robot/drone))
thing.throw_at(target, 3, 1)
/obj/structure/disposaloutlet/proc/post_post_expel(obj/structure/disposalholder/H)
disposal_log("[src] [REF(src)] post_post_expel([REF(H)]), [H.contents.len] movables")
for(var/atom/movable/AM in H)
AM.forceMove(src.loc)
AM.pipe_eject(dir)
if(!istype(AM,/mob/living/silicon/robot/drone)) //Drones keep smashing windows from being fired out of chutes. Bad for the station. ~Z
AM.throw_at(target, 3, 1)
qdel(H)
*/
/obj/structure/disposaloutlet/attackby(obj/item/attacking_item, mob/user)
if(!attacking_item || !user)
return
src.add_fingerprint(user)
if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
if(mode==0)
mode=1
attacking_item.play_tool_sound(get_turf(src), 50)
to_chat(user, "You remove the screws around the power connection.")
return
else if(mode==1)
mode=0
attacking_item.play_tool_sound(get_turf(src), 50)
to_chat(user, "You attach the screws around the power connection.")
return
else if(attacking_item.tool_behaviour == TOOL_WELDER && mode==1)
var/obj/item/weldingtool/W = attacking_item
if(W.use(0,user))
to_chat(user, "You start slicing the floorweld off the disposal outlet.")
if(W.use_tool(src, user, 20, volume = 50))
if(!src || !W.isOn()) return
to_chat(user, "You slice the floorweld off the disposal outlet.")
var/obj/structure/disposalconstruct/C = new (src.loc)
src.transfer_fingerprints_to(C)
C.ptype = 7 // 7 = outlet
C.update()
C.anchored = 1
C.density = 1
qdel(src)
return
else
to_chat(user, "You need more welding fuel to complete this task.")
return
/**
* Called when movable is expelled from a disposal pipe or outlet
* By default does nothing, override for special behaviour
*/
/atom/movable/proc/pipe_eject(var/direction)
return
/**
* Check if mob has client, if so restore client view on eject
*/
/mob/pipe_eject(var/direction)
if (src.client)
src.client.perspective = MOB_PERSPECTIVE
src.client.eye = src
return
/obj/effect/decal/cleanable/blood/gibs/pipe_eject(var/direction)
var/list/dirs
if(direction)
dirs = list( direction, turn(direction, -45), turn(direction, 45))
else
dirs = GLOB.alldirs.Copy()
src.streak(dirs)
/obj/effect/decal/cleanable/blood/gibs/robot/pipe_eject(var/direction)
var/list/dirs
if(direction)
dirs = list( direction, turn(direction, -45), turn(direction, 45))
else
dirs = GLOB.alldirs.Copy()
src.streak(dirs)